As the foundation on which other synchronization methods are built, atomic operations provide instructions that execute atomically, without interruption. Atomic operators are indivisible instructions. For example, an atomic increment can read and increment a variable by one in a single indivisible and uninterruptible step. Consider in a multi-threaded application, a function is incrementing a global/static variable,
// count has permanent storage in RAM count++;
The above statement can be decomposed into below three operations.
- Fetching count value
- Incrementing count value
- Storing the updated value
If a thread executing the function containing the above statement is fetching its value (say 2). It is possible that at this point of execution, the thread can be preempted and another thread may invoke the same function. Consequently, the value of count will be incremented to 3 by that thread. When the former thread is resumed, it still retains the previous value (2), instead of latest value (3), and ends up in writing back 3 again. Infact, the value of count should be 4 due to affect of both the threads. During an atomic operation, a processor can read and write a location during the same data transmission.
Another example,
Thread 1 | Thread 2 |
---|---|
Get count (7) | Get count (7) |
Increment count (7 -> 8) | — |
— | Increment count (7 -> 8) |
Write back count (8) | — |
— | Write back count (8) |
Due to race around between two threads, val; two threads might interleave their operations as shown in the above table, where time progresses downwards. With atomic operators, this race does cannot—occur. Instead, the outcome is always one of the following:
Thread 1 | Thread 2 |
---|---|
Get, Increment, and Store count (7 -> 8) | — |
— | Get, Increment, and Store count (8 -> 9) |
Or:
Thread 1 | Thread 2 |
---|---|
— | Get, Increment, and Store count (7 -> 8) |
Get, Increment, and Store count (8 -> 9) | — |
The ultimate value, always nine, is correct. It is never possible for the two atomic operations to occur on the same variable concurrently. Therefore, it is not possible for the increments to race.
Most architectures contain instructions that provide atomic versions of simple arithmetic operations. Other architectures, lacking direct atomic operations, provide an operation to lock the memory bus for a single operation, thus guaranteeing that another memory-affecting operation cannot occur simultaneously