Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does atomic read guarantees reading of latest value?

In C++ we have keyword volatile and atomic class. Difference between them that volatile does not guarantees thread-safe concurrent reading and writing, but just ensures that compiler will not store variable's value in cache and instead will load variable from memory, while atomic guarantees thread-safe concurrent reading and writing.

As we know, atomic read operation indivisible, i.e. neither thread can not write new value to variable while one or more threads reading variable's value, so I think that we always read the latest value, but I'm not sure :)

So, my question is: if we declare atomic variable, do we always get the latest value of the variable calling load() operation?

like image 537
TwITe Avatar asked Oct 28 '18 14:10

TwITe


People also ask

Are memory reads Atomic?

Storing values to memory and reading values from memory are atomic operations. ¹ If a competing processor writes to or reads the same memory, the result will be completely one value or the other, never a mix of the two. This atomicity does not extend by default to read-modify-write operations, however.

Why are read and write Atomic?

Generally, you can summarize atomic as "one at a time". For example, when accessing or mutating a property is atomic, it means that only one read or write operation can be performed at a time. If you have a program that reads a property atomically, this means that the property cannot change during this read operation.

When to use atomic operations?

Atomic operations are sequences of instructions that guarantee atomic accesses and updates of shared single word variables. This means that atomic operations cannot protect accesses to complex data structures in the way that locks can, but they provide a very efficient way of serializing access to a single word.

Why atomic instructions are important?

Abstract. Atomic instructions atomically access and update one or more memory locations. Because they do not incur the overhead of lock acquisition or suspend the executing thread during contention, they may allow higher levels of concurrency on multiprocessors than lock-based synchronization.


2 Answers

When we talk about memory access on modern architectures, we usually ignore the "exact location" the value is read from.

A read operation can fetch data from the cache (L0/L1/...), the RAM or even the hard-drive (e.g. when the memory is swapped).

These keywords tell the compiler which assembly operations to use when accessing the data.

volatile

A keyword that tells the compiler to always read the variable's value from memory, and never from the register.

This "memory" can still be the cache, but, in case that this "address" in the cache is considered "dirty", meaning that the value has changed by a different processor, the value will be reloaded.

This ensures we never read a stale value.

But, if the type declare volatile is not a primitive, whos read/write operations are atomic (in regard to the assembly instructions that read/write it) by nature, we might read an intermediate value (the writer managed to write only half of the bytes by the time the reader read it).

atomic

And the compiler sees a load (read) operation, it basically does the exact same thing it would have done for a volatile value, except for using atomic operations (This means that we will never read an intermediate value).

So, what is the difference???

The difference is cross-CPU write operations. When working with a volatile variable, if CPU 1 sets the value, and CPU 2 reads it, the reader might read an old value.

But, how can that be? The volatile keyword promises that we won't read a stale value!

Well, that's because the writer didn't publish the value! And though the reader tries to read it, it reads the old one.

When the compiler stumbles upon a store (write) operation for an atomic variable it:

  • Sets the value atomically in memory
  • Announces that the value has changed

After the announcement, all the CPUs will know that they should re-read the value of the variable because their caches will be marked "dirty".

This mechanism is very similar to operations performed on files. When your application writes to a file on the hard-drive, other applications may or may not see the new information, depending on whether or not your application flushed the data to the hard-drive.

If the data wasn't flushed, then it merely resides somewhere in your application's caches and visible only itself. Once you flush it, anyone who opens the file will see the new state.

like image 91
Daniel Trugman Avatar answered Sep 20 '22 21:09

Daniel Trugman


if we declare atomic variable, do we always get the latest value of the variable calling load() operation?

Yes, for some definition of latest.

The problem with concurrency is that it is not possible to argue about order of events in the usual way. This comes from a fundamental limitation in the hardware where the only way to establish a global order of operations across multiple cores would be to serialize them (and eliminating all of the performance benefits of parallel computation in the process).

What modern processors provide instead is an opt-in mechanism to re-establish order between certain operations. Atomics are the language-level abstraction for that mechanism. Imagine a scenario in which two atomic<int>s a and b are shared between threads (and let's further assume they were initialized to 0):

// thread #1
a.store(1);
b.store(1);

// thread #2
while(b.load() == 0) { /* spin */ }
assert(a.load() == 1);

The assertion here is guaranteed to hold. Thread #2 will observe the "latest" value of a.

What the standard does not talk about is when exactly the loop will observe the value of b changing from 0 to 1. We know it will happen some time after the write by thread #1 and we also know it will happen after the write to a. But we don't know how long after.

This kind of reasoning is further complicated by the fact, that different threads are allowed to disagree when certain writes took place. If you switch to a weaker memory ordering, one thread may observe writes to distinct atomic variables happening in a different order than what is observed by another thread.

like image 37
ComicSansMS Avatar answered Sep 22 '22 21:09

ComicSansMS