Reclaim Hazard Pointer and hazardpointer in parallel programming
In the previous article, the RCU technology is used to implement Lock-free read/write threads. In languages without GC mechanisms, to implement the Lock-free algorithm, you will inevitably have to handle the issue of memory Reclaim by yourself.
Hazard Pointer is another algorithm used to solve this problem. It is not only simple, but also powerful. The data structure unrelated to the lock is well described in the Hazard pointer, And Wikipedia Hazard pointer is also clearly described, so I will not elaborate on it here.
For a simple implementation, refer to my github haz_ptr.c
Principle
The basic principle is that the read thread identifies the pointer. When the pointer (pointing to the memory) is to be released, the cache will be delayed until it is confirmed that there is no read thread to release it.
<Lock-Free Data Structures with Hazard Pointers>
Description in:
Each reader thread owns a single-writer/multi-reader shared pointer called "hazard pointer. "When a reader thread assigns the address of a map to its hazard pointer, it is basically announcing to other threads (writers)," I am reading this map. you can replace it if you want, but don't 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 uses 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; // wrapped 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 stores the pointer list to be released. This list only corresponds to the thread read/write
void defer_free(void *ptr) { _free_list.push_back(ptr); }
When a thread tries to release the pointer in the Free List, for example, pointerptr
Check the Hazard pointer used by all other threads and check whether the package exists.ptr
If not, it indicates that no read thread is usingptr
, Can be safely releasedptr
.
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 } }
The above is actuallyHazard Pointer
.
Management of Hazard Pointer
Not mentioned in the above Code_all_hazard_pointers
Andaccquire
This is the management problem of Hazard Pointer.
In the data structure unrelated to the Lock and Hazard Pointer, a Lock free linked List is created to represent the global Hazard Pointer List. Each Hazard Pointer has a member to identify whether it is available. This List stores the used Hazard Pointer set and the unused Hazard Pointer set. When all Hazard pointers are used, a new one will be allocated and added to this List. When the read thread does not use a Pointer, return the Hazard Pointer and directly set the available Member ID. Yesgc()
Directly traverse the List.
It is very easy to implement a Lock free linked list and only implement header insertion. When Hazard Pointer identifies a Pointer, the Pointer is immediately identified. Therefore, this implementation directly supports dynamic threads and thread suspension.
There is also a Hazard Pointer implementation in the nbds project, which is relatively weaker. It sets its own Hazard Pointer pool for each thread. When the writing thread wants to release the Pointer, It accesses the Hazard Pointer pool of all other threads.
Typedef struct haz_local {// Free List pending_t * pending; // to be freed int pending_size; int pending_count; // Hazard Pointer pool, two types 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] = {};
Of course, each thread involveshaz_local_
The allocation of indexes (IDS) is the same as that of read/write threads without locks using RCU technology. To support dynamic creation of threads, a thread ID reuse mechanism is required, which is much more complicated.
Appendix
Finally, some concepts in parallel programming are attached.
Lock Free & Wait Free
Often seeLock Free
AndWait Free
Concepts. These concepts are used to measure the parallel level of a system or code segment. For details about the parallel level, see parallel programming-concurrency level. In short, Wait Free is a more powerful level than Lock Free.
In my own understanding, for example, the Hazard Pointer linked list implemented in "Lock-independent data structure and Hazard Pointer" can be said to be Lock Free. Note that when inserting new elements into the chain table header, becauseCAS
, There is always a busy loop, even if this feature existsLock Free
, Although there is no lock, the execution of a thread is also affected by other threads.
Relatively speaking,Wait Free
The execution of each thread is independent. For example, in the data structure unrelated to the lock and the Hazard pointerScan
Function."The execution time of each thread does not depend on the behavior of any other thread"
Lock-Free means that there is always a thread in the system that can continue to execute; while Wait-Free is a stronger condition, it means that all threads can proceed.
ABA Problems
In implementationLock Free
In the process of algorithm, always useCAS
Primitive, andCAS
It will bringABA
Problem.
During the CAS operation, CAS mainly asks "whether the value of V is still A" before changing the value of V. Therefore, after reading V for the first time and before performing the CAS operation on V, if you change the value from A to B and then back to A, the CAS-based algorithm will be chaotic. In this case, the CAS operation is successful. Such problems are called ABA problems.
Wiki Hazard Pointer mentioned a good example of ABA problem: in a Lock free stack implementation, the elements in the stack are[A, B, C]
,head
Point to the top of the stack.compare_and_swap(target=&head, newvalue=B, expected=A)
. However, in this operation, other threadsA
B
All are out of the stack and deletedB
, AndA
In the stack, that is[A, C]
. Then the previous thread'scompare_and_swap
Successful.head
Point to a deletedB
. Stackoverflow also has an example of Real-world examples for ABA in multithreading.
The common solution for this ABA problem produced by CAS is to use a variant DCAS of CAS. DCAS adds a referenced identifier for each V to indicate the number of modifications. For each V, if the reference is modified once, this counter is added with 1. Then, when the variable needs to be updated, both the value of the variable and the value of the counter are checked.
But some people have proposedDCAS
It is not a silver bullet of ABA problem.
Address: http://codemacro.com/2015/05/03/hazard-pointer/
Written by Kevin Lynx posted athttp: // codemacro.com