After the use of RCU technology to realize the read-write thread unlocked, in the absence of a GC mechanism in the language, to implement the lock free algorithm, it is unavoidable to deal with the memory recovery problem.
Hazard pointer is another algorithm to deal with this problem, and it is not only simple but also powerful. The lock-independent data structure and Hazard pointers are very good, Wikipedia Hazard pointer is also described more clearly, so I'm not so thin here.
A simple implementation can refer to my GitHub haz_ptr.c
Principle
The basic principle is that a read thread identifies a pointer, and the pointer (the memory pointed to) is cached when it is released and deferred until it is confirmed that there is no read thread to actually release it.
<Lock-Free Data Structures with Hazard Pointers>
In the description:
Each reader thread owns a single-writer/multi-reader shared pointer called "Hazard pointer." When a reader thread assigns the address of a maps to its hazard pointer, it's basically announcing to other threads (writ ERS), "I am reading this map. You can replace it with it if you want, but Don's change its contents and certainly keep your deleteing hands off it. "
Key structures include: Hazard pointer
,Thread Free list
Hazard pointer
: When a read thread is to use a pointer, a hazard pointer is created to wrap the pointer. A hazard pointer will be written by one thread and read by multiple threads.
struct Hazardpointer { void *real_ptr;//wrapper pointer ...// different implementations have different members }; void Func () { Hazardpointer *hp = Accquire (_real_ptr); ...//Use _real_ptr release (HP); }
Thread Free List
: Each thread has a list that holds the list of pointers that will be freed, and this list is read-only by the corresponding thread
void Defer_free (void *ptr) { _free_list.push_back (PTR); }
When a thread tries to release a pointer in the free list, such as a pointer ptr
, it checks the hazard pointer used by all other threads, checks for the existence of a wrapped ptr
hazard pointer, and if not, indicates that no read thread is in use ptr
and can be safely released ptr
.
void GC () {for (ptr in _free_list) { conflict = False for (HP in _all_hazard_pointers) { if (Hp->_real_ ptr = = ptr) { confilict = True break } } if (!conflict) delete ptr } }
Above, in fact is Hazard Pointer
the main content.
Management of Hazard pointer
The above code does not mention _all_hazard_pointers
accquire
the specific implementation, this is hazard pointer management issues.
The lock free data structure and hazard pointers create a linked list of lock free to represent this global hazard Pointer List. Each hazard pointer has a member that identifies whether it is available. The list also preserves the collection of hazard pointer that have been used and the unused hazard pointer collection, and when all hazard pointer are used, a new one is added to the list. When a read thread does not use a pointer, it needs to return the hazard Pointer and set the available member identifiers directly. If you want gc()
, simply traverse the list.
It is very simple to implement a linked list of lock free and only need to implement a header insertion. itself hazard pointer identifies a pointer, it is used immediately after identification, so this implementation directly supports the dynamic thread, support thread hangs and so on.
In the NBDs project there is also a hazard pointer implementation, relatively weaker. It sets its own hazard pointer pool for each thread, and when the write thread wants to release the pointer, it accesses the hazard pointer pool for all other threads.
typedef struct HAZ_LOCAL { //free List pending_t *pending,//To be freed int pending_size; int pending_count; Hazard Pointer Pond, dynamic and static two kinds of haz_t static_haz[static_haz_per_thread]; haz_t **dynamic; int dynamic_size; int dynamic_count; } __ATTRIBUTE__ ((Aligned (cache_line_size))) haz_local_t; Static haz_local_t Haz_local_[max_num_threads] = {};
Each thread, of course haz_local_
, involves the allocation of an index (ID), just like using RCU technology to implement a read-write thread without a lock. This implementation, in order to support thread dynamic creation, requires a set of thread ID reuse mechanisms, relatively more complex.
Appendix
Finally, some concepts in parallel programming are attached.
Lock Free & Wait Free
Often seen Lock Free
and Wait Free
concepts, these concepts are used to measure the parallel level of a system or a piece of code, and the parallel level can refer to parallel programming-the concurrency level. Anyway, wait free is a much better level than lock free.
My own understanding, such as the "lock-independent data structure and hazard pointer" Implementation of the hazard pointer linked list can be said to be lock free, note that it is inserted into the new element to the list header, because CAS
the use of a busy loop is always unavoidable, In the case of this feature, the Lock Free
execution of a thread is affected by other threads, although it is not locked.
In contrast, the Wait Free
execution of each thread is independent, such as the function in the lock-independent data structure and hazard pointers Scan
.“每个线程的执行时间都不依赖于其它任何线程的行为”
Lock-Independent (lock-free) means that there is always a thread in the system that can continue to execute, while waiting for nothing (Wait-free) is a stronger condition, which means that all threads can go down.
ABA Issues
In the implementation Lock Free
of the algorithm, always use CAS
the original language, and CAS
will bring ABA
problems.
In the case of CAS operations, because the CAS primarily asked "V's value is still a" before changing V, the CAS-based algorithm would be confusing if the value was changed from a to B and then to a after the first read of V, and before the CAS operation was performed on V. In this scenario, the CAS operation succeeds. This kind of problem is called ABA problem.
Wiki Hazard Pointer mentioned a good example of an ABA problem: in a lock free stack implementation, now to the stack, the stack of elements is [A, B, C]
, point to the head
top of the stack, then there is compare_and_swap(target=&head, newvalue=B, expected=A)
. But in this operation, the other threads A
B
are all out of the stack, deleted B
, and A
pressed into the stack, that is [A, C]
. Then the previous thread compare_and_swap
succeeds, head
pointing to an already deleted one B
. StackOverflow also has an example Real-world examples for ABA in multithreading
The usual solution for this ABA problem from CAS is to use a variant of CAs DCAs. DCAS, which is a token for each V to add a reference to the number of modifications. For each V, if the reference is modified once, the counter is incremented by 1. Then, when the variable needs to be update, it checks both the value of the variable and the value of the counter.
But it was also early DCAS
to suggest that it was not an ABA problem silver bullet.
Original address: http://codemacro.com/2015/05/03/hazard-pointer/
Written by Kevin Lynx posted athttp://codemacro.com
Memory recovery hazard Pointer in parallel programming