In modern operating systems, atomic operations are generally provided to achieve some synchronous operations, the so-called atomic operation, which is an independent and indivisible operation. In a single-core environment, the normal meaning of the atomic operation is not switched, the thread is switched either before the atomic operation or after the atomic operation is complete. In a broader sense, an atomic operation is a sequence of steps that must be completed in its entirety, and if any of the steps are not completed, then all the completed procedures must be rolled back so that either all the steps are not completed or all the steps are completed.
For example, in a single-core system, a single machine instruction can be seen as an atomic operation (except in the case of compiler optimizations, random execution, and so on); in multicore systems, a single machine instruction is not atomic, because multiple-instruction streams run in parallel in a multicore system, and a core executes an instruction Other cores execute concurrently with the possibility of manipulating the same memory area, resulting in data contention. Atomic operations in multicore systems are typically implemented using memory barrier, which means that when a CPU core performs atomic operations, the other CPU cores must stop operating on the memory or do not operate on the specified memory in order to avoid data contention.
There are three main types of atomic operations commonly used in the Win32 API, one is to add 1 minus 1 operations, one is to compare switching operations, and the other is to assign (write) operations.
1. Atom plus 1 minus 1 operation
The atomic Plus and minus operations mainly add 1 or minus 1 to the specified variable, and the atomic plus 1 operation API is as follows:
Long InterlockedIncrement (long volatile* Addend);
Performing an atomic plus 1 operation, the value of Addend is added 1, and the return value is the value after the original Addend plus 1. It is important to note that when the operation is finished, the value of the addend is not necessarily equal to the value after the original Addend plus 1, because it is possible that other threads will modify the value of Addend.
The API for the atomic minus 1 operation is as follows:
Long InterlockedDecrement (long volatile* Addend);
Performing an atomic minus 1 operation, the value of the Addend is reduced by 1, and the return value is the value after the original Addend minus 1. As with Atomic Plus, when the operation is finished, the value of the addend is not necessarily equal to the value after the original Addend minus 1 because it is possible that other threads will modify the value of Addend.
Therefore, when using this type of atomic operation plus 1 and minus 1, if you want to take the value of 1 or minus 1, be sure to take the return value, not to take the original variable value. In the p190~191 page of multi-core programming technology-through software multithreading to improve performance, it also explains how to safely use atomic operations for counting, and the basic idea is still to take the return value of atomic operations.
2. Compare and Exchange operations (Compare and Swap, CAS)
The purpose of the compare and exchange operation is to compare the target value to the old value within an atomic operation, and assign the new value to the target variable if it is equal. The goal is to prevent the target variable from being modified by other threads between read and write operations before the new value is assigned to the target variable.
The CAS internal operation process is shown in the following pseudo-code:
BOOL CAS (Long *target, long new, long old) {
atomically {//The following as a whole, is indivisible
if (*target = = old) {
*target = new;
return true;
}
else {
return false;
}
}
}
Note that the above pseudo-code only demonstrates the operation of CAs, in fact, the implementation of the entire operation is inseparable.
In the Microsoft API, the CAS atomic operation corresponds to InterlockedCompareExchange () and if it is a swap pointer, then the corresponding operation is Interlockedcompareexchangepointer ().
InterlockedCompareExchange () with a global memory barrier, the corresponding operation without a global memory gate barrier is Interlockedcompareexchangeacquire () or Interlockedcompareexchangerelease ()
The InterlockedCompareExchange () function is prototyped as follows:
Long InterlockedCompareExchange (long volatile*destination, long Exchange, long Comperand);
This operation compares the value of Comperand (the old parameter of the previous CAs) to the value of the destination (which is equivalent to the target parameter of the previous CAs) to the values of the variable. If equality assigns the value of exchange equivalent to the new parameter of the previous CAS) variable to the variable that destination points to. The return value is the initial value of the destination position before it was modified.
The following uses Atomiccas () to represent the CAS operation, the source code of Atomiccas is implemented as follows:
BOOL Atomiccas (long volatile *dest, long newvalue, long oldvalue)
{
LONG ret;
ret = InterlockedCompareExchange (dest, NewValue, oldvalue);
if (ret = = oldvalue)
{
return TRUE;
}
Else
{
return FALSE;
}
}
CAS operations are the most basic atomic operations, and other atomic operations can actually be implemented by this atomic operation through a simple algorithm. For example, the above-mentioned Atom plus 1 operation can be implemented with the following code:
long atomicincrement (long volatile *target)
{
LONG old;
do {
Old = *target;
}while (! Atomiccas (Target, old + 1, old));
return old;
}
The above code uses a loop to operate, because "old = *target" is executed every time. After this statement, it is possible that the thread has just switched, other threads are likely to modify the value of target, if the value of old+1 is also assigned to target, then an error will occur, then the Atomiccas operation will fail, the need to rollback (that is, re-cycle) to operate, Until the Atomiccas operation succeeds.
From the above using the CAS implementation of Atomicincrement code can also see that the role of CAS is to prevent the sharing of variables two operations between the thread switch, so need to be in an atomic operation first to compare the target value and the old value, if the old value is equal, Indicates that no other thread has modified the value of the target variable, and it is safe to modify the value of the target.
3. Atomic Write operations
The function of the atomic write operation is to write to the shared variable, in the Microsoft API, the corresponding function of the atomic write operation is:
Long InterlockedExchange (Long volatile* Target, long Value);
The role of InterlockedExchange is to assign value to the variable that the target points to, and return the value before the target pointer to the variable is not assigned value.
The following uses Atomicwrite () to represent the atomic assignment operation,
#define Atomicwrite (x, y) interlockedexchange (x, y)
The atomic write operation can also be implemented with the previous CAS operation, with the following code:
long atomicwrite (long volatile *target, long Value)
{
LONG old;
do {
Old = *target;
}while (! Atomiccas (Target, Value, old));
return old;
}
In the atomic operations described above, CAS atomic operations are relatively complex atomic operations, using CAS atomic manipulation programming also called lock-free programming. No lock programming is very difficult, non-professionals do not try.
Atomic plus 1 minus 1 operation is a very useful atomic operation, but also the programmer is relatively easy to master atomic operations, such as the following two articles in the use of atomic plus 1 minus 1 operations.