In the previous two blogs (the implementation of thread-safe unlocked ringbuffer, a non-lock queue implementation of a read thread for multiple write threads), it was written that in the case of only one read thread, one write thread, and only one write thread, two read thread, no lock technology was used, Even the implementation of the cyclic queue of atomic operations. However, in other cases, we also need to implement the thread-safe queue as efficiently as possible. This paper implements a lock-free queue based on cyclic arrays and atomic operations. Using atomic operations (compare and Swaps) instead of lock-in synchronization can greatly improve operational efficiency. The reason for using the loop array is that it does not need to open up the memory space repeatedly in the process, and can achieve better efficiency. The implementation of this paper refers to the method mentioned in the paper implementing Lock-free queues. However, in this paper, the author does not give a concrete implementation. The implementation of this article is also different from the method in this paper. The implementation of this article is based on the Windows operating system, and the code is tested under Visual Studio 2010.
The implementation of this article is based on the atomic operation of Compare and swap, abbreviated to CAS. Its prototype is
Long CAS (long* ptr, long old_value, long New_value).
The action is that if PTR points to a variable that is equal to the old_value, it is set to New_value. Or do nothing. The return value is the variable that PTR points to.
The basic principles of the method presented in this paper are as follows. The code will be appended later.
First of all, we agreed that there is a special value called empty, and all the numbers in the array cannot be this value. At the beginning, the entire buffer value is initialized to empty.
Second, take two variables, readcnt and writecnt, to record the number of reads and writes. These two-module lengths allow you to get the next read and write position. If the readcnt and writecnt buffer sizes are equal, the current team is listed as controlled, and if the write location is not empty, the buffer is full.
The third, and most important, principle of thread synchronization. In the write buffer operation, the buffer corresponding position is set to the specified value by CAS operation, and writecnt is not changed, so other threads cannot write at the same location, thus preventing overwriting of other write threads. Because the writecnt has not changed at this point, the read thread cannot read the data at this point, thus preventing other read threads from conflicting. Next, the second step is to add a writecnt to the CAS operation. As you can see, in the code where the first CAS operation fails, an atomic operation that increases writecnt is performed. The purpose of this is to prevent one thread from stopping and causing all other threads to stop.
In the case of write operations, it is first determined whether the current queue is full by judging whether readcnt and writecnt are equal. The reason for doing this is to avoid and write conflicts between threads, as described earlier. Next, if the current location is readable, readcnt is added by one CAS operation. This way, other read threads cannot read this location again. And because this location value is not empty, other write threads cannot write this location. Next, after reading the values, this position in buffer is set to empty by the CAS operation. At this point, the write thread can write data at this location.
However, this implementation is actually a bit problematic. If the read thread is more, for example, the number of read threads and the length of the array, it is possible for two read threads to read the same location at the same time. This process of reading conflicts is unavoidable. Also, if a read thread is paused while reading the data, other threads will be blocked when they read or write to that location.
The code is as follows.
1#include <windows.h>2#include <stdlib.h>3 4 #defineCAS (PTR, Oldval, newval) (InterlockedCompareExchange (PTR, newval, oldval))5 6 Static Const Longempty=-1;7 8 classNbqueue {9 Public:Ten Nbqueue () One: Queuesize (0) A , buffer (NULL) -, Readcnt (0) -, Writecnt (0){} the -~Nbqueue () { - uninit (); - } + - BOOLInitintsize) { +Queuesize =size; A Try { atBuffer =New Long[queuesize]; - } - Catch (...) { -Queuesize =0; -Buffer =NULL; - return false; in } - for(inti =0; i < queuesize; i++) { toBuffer[i] =EMPTY; + } - return true; the } * voidUnInit () { $ if(Buffer! =NULL) {Panax Notoginseng delete[] buffer; -Buffer =NULL; the } +Queuesize =0; A } the + BOOLPutLongv) { - BOOLsucc; $ Longtail; $ Longindex; - Do { -Tail =writecnt; theindex = tail%queuesize; - if(Buffer[index]! =EMPTY) {Wuyi return false; the } - LongOldval = CAS (&Buffer[index], EMPTY, v); WuSUCC = Oldval = =EMPTY; - if(!succ) { AboutCAS (&writecnt, tail, tail +1); $ } -} while(!succ); - -CAS (&writecnt, tail, tail +1); A + return true; the } - $ BOOL Get(Long*v) { the Longhead; the BOOLsucc; the Do { theHead =readcnt; - if(head = = writecnt%queuesize) { in return false; the } the LongOldval = CAS (&readcnt, head, head +1); AboutSUCC = Oldval = =head; the} while(!succ); the the Longindex = head%queuesize; +*v =Buffer[index]; -CAS (&buffer[index], *V, EMPTY); the Bayi return true; the } the - Private: - Long*buffer; the intqueuesize; the Longreadcnt; the Longwritecnt; the};
A lock-free queue based on loop array