13
votes

From what I know, the compiler never optimizes a variable that is declared as volatile. However, I have an array declared like this.

volatile long array[8];

And different threads read and write to it. An element of the array is only modified by one of the threads and read by any other thread. However, in certain situations I've noticed that even if I modify an element from a thread, the thread reading it does not notice the change. It keeps on reading the same old value, as if compiler has cached it somewhere. But compiler in principal should not cache a volatile variable, right? So how come this is happening.

NOTE: I am not using volatile for thread synchronization, so please stop giving me answers such as use a lock or an atomic variable. I know the difference between volatile, atomic variables and mutexes. Also note that the architecture is x86 which has proactive cache coherence. Also I read the variable for long enough after it is supposedly modified by the other thread. Even after a long time, the reading thread can't see the modified value.

10
volatile is not for threads. You need to use a mutex. - Vaughn Cato
AFAIK in C++ volatile only affects the compiler optimizations, not the possible CPU reorders that can still happen. - Tudor
Your note doesn't affect my answer, btw, and probably not other people's answers. You might not think you're using volatile for thread synchronization, but if you expect it to introduce a relationship between a read and a write in different threads, then in point of fact you are, because by definition that's what thread synchronization is. - Steve Jessop
@Eric: it explicitly states an expectation about when the readers will see updates: "I read the variable for long enough after is is supposedly modified by the other thread". Assuming the test code is doing what it's intended to, this is an argument between user1018562 and his implementation, over how long is "long enough". He says there's a limit, the implementation apparently says there isn't. The standard will not intervene in that argument, it has nothing to say about whether the compiler "in principal should not cache a volatile variable", because the code has a data race. - Steve Jessop
I would try a mutex. If that fixes it, then you can expect it is a cache issue. If that doesn't fix it, then you can look for the problem elsewhere. - Vaughn Cato

10 Answers

7
votes

But compiler in principal should not cache a volatile variable, right?

No, the compiler in principle must read/write the address of the variable each time you read/write the variable.

[Edit: At least, it must do so up to the point at which the the implementation believes that the value at that address is "observable". As Dietmar points out in his answer, an implementation might declare that normal memory "cannot be observed". This would come as a surprise to people using debuggers, mprotect, or other stuff outside the scope of the standard, but it could conform in principle.]

In C++03, which does not consider threads at all, it is up to the implementation to define what "accessing the address" means when running in a thread. Details like this are called the "memory model". Pthreads, for example, allows per-thread caching of the whole of memory, including volatile variables. IIRC, MSVC provides a guarantee that volatile variables of suitable size are atomic, and it will avoid caching (rather, it will flush as far as a single coherent cache for all cores). The reason it provides that guarantee is because it's reasonably cheap to do so on Intel -- Windows only really cares about Intel-based architectures, whereas Posix concerns itself with more exotic stuff.

C++11 defines a memory model for threading, and it says that this is a data race (i.e. that volatile does not ensure that a read in one thread is sequenced relative to a write in another thread). Two accesses can be sequenced in a particular order, sequenced in unspecified order (the standard might say "indeterminate order", I can't remember), or not sequenced at all. Not sequenced at all is bad -- if either of two unsequenced accesses is a write then behavior is undefined.

The key here is the implied "and then" in "I modify an element from a thread AND THEN the thread reading it does not notice the change". You're assuming that the operations are sequenced, but they're not. As far as the reading thread is concerned, unless you use some kind of synchronization the write in the other thread hasn't necessarily happened yet. And actually it's worse than that -- you might think from what I just wrote that it's only the order of operations that is unspecified, but actually the behavior of a program with a data race is undefined.

4
votes

C

What volatile does:

  • Guarantees an up-to-date value in the variable, if the variable is modified from an external source (a hardware register, an interrupt, a different thread, a callback function etc).
  • Blocks all optimizations of read/write access to the variable.
  • Prevent dangerous optimization bugs that can happen to variables shared between several threads/interrupts/callback functions, when the compiler does not realize that the thread/interrupt/callback is called by the program. (This is particularly common among various questionable embedded system compilers, and when you get this bug it is very hard to track down.)

What volatile does not:

  • It does not guarantee atomic access or any form of thread-safety.
  • It cannot be used instead of a mutex/semaphore/guard/critical section. It cannot be used for thread synchronization.

What volatile may or may not do:

  • It may or may not be implemented by the compiler to provide a memory barrier, to protect against instruction cache/instruction pipe/instruction re-ordering issues in a multi-core environment. You should never assume that volatile does this for you, unless the compiler documentation explicitly states that it does.
3
votes

With volatile you can only impose that a variable is re-read whenever you use its value. It doesn't guarantee that the different values/representations that are present on different levels of your architecture are consistent.

To have such gurantees you'd need the new utilities from C11 and C++1 concerning atomic access and memory barriers. Many compilers implement these already in terms of extension. E.g the gcc family (clang, icc, etc) have builtins starting with prefix __sync to implement these.

2
votes

Volatile Keyword only guarantees that the compiler will not use register for this variable. Thus every access to this variable will go and read the memory location. Now, I assume that you have cache coherence among the multiple processors in your architecture. So if one processor writes and other reads it, then it should be visible under normal conditions. However, you should consider the corner cases. Suppose the variable is in the pipeline of one processor core and other processor is trying to read it assuming that has been written, then there is a problem. So essentially, the shared variables should be either guarded by locks or should be protected by using barrier mechanism correctly.

2
votes

The semantics of volatile are implementation-defined. If a compiler knew that interrupts would be disabled while certain piece of code was executed, and knew that on the target platform there would be no means other than interrupt handlers via which operations on certain storage would be observable, it could register-cache volatile-qualified variables within such storage just the same as it could cache ordinary variables, provided it documented such behavior.

Note that what aspects of behavior are counted as "observable" may be defined in some measure by the implementation. If an implementation documents that it is not intended for use on hardware which uses main RAM accesses to trigger required externally-visible actions, then accesses to main RAM would not be "observable" on that implementation. The implementation would be compatible with hardware which was capable of physically observing such accesses, if nothing cared whether any such accesses were actually seen. If such accesses were required, however, as they would be if the accesses were regarded as "observable", however, the compiler would not be claiming compatibility and would thus make no promise about anything.

1
votes

For C++:

From what I know, the compiler never optimizes a variable that is declared as volatile.

Your premise is wrong. volatile is a hint to the compiler and doesn't actually guarantee anything. Compilers can choose to prevent some optimizations on volatile variables, but that's it.

volatile isn't a lock, don't try to use it as such.

7.1.5.1

7) [ Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9 for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. —end note]

1
votes

Volatile only affects the variable it is in front of. Here in your example, a pointer. Your code: volatile long array[8], the pointer to the first element of the array is volatile, not it's content. (same for objects of any kind)

you could adapt it like in How do I declare an array created using malloc to be volatile in c++

1
votes

The volatile keyword has nothing to do with concurrency in C++ at all! It is used to have the compiler prevented from making use of the previous value, i.e., the compiler will generate code accessing a volatile value every time is accessed in the code. The main purpose are things like memory mapped I/O. However, use of volatile has no affect on what the CPU does when reading normal memory: If the CPU has no reason to believe that the value changed in memory, e.g., because there is no synchronization directive, it can just use the value from its cache. To communicate between threads you need some synchronization, e.g., an std::atomic<T>, lock a std::mutex, etc.

0
votes

C++ accesses by volatile lvalues and C accesses to volatile objects are "abstractly" "observable"--although in practice C behaviour is per the C++ standard not the C standard. Informally, the volatile declaration tells every thread the value might change somehow regardless of the text in any thread. Under the Standards with threads, there isn't any notion of a write by another thread causing a change in an object, volatile or not, shared or not, except for a shared variable by the synchronizing function call at the start of a synchronized critical region. volatile is irrelevant to thread shared objects.

If your code doesn't properly synchronize the thread you are talking about, your one thread reading what an other thread wrote has undefined behaviour. So the compiler can generate any code it wants. If your code is properly synchronized then writes by other threads only happen at thread synchronization calls; you don't need volatile for that.

PS

The standards say "What constitutes an access to an object that has volatile-qualified type is implementation-defined." So you can't just assume that there is a read access for every dereferencing of a volatile lvalue or a write acces for every assignment through one.

Moreover how ("abstract") "observable" volatile accesses are "actually" manifested is implementation defined. So a compiler might not generate code for hardware accesses corresponding to the defined abstract accesses. Eg maybe only objects with static storage duration and external linkage compiled with a certain flag for linking into special hardware locations can ever be changed from outside the program text, so that other objects' volatile is ignored.

-1
votes

However, in certain situations I've noticed that even if I modify an element from a thread, the thread reading it does not notice the change. It keeps on reading the same old value, as if compiler has cached it somewhere.

This is not because the compiler cached it somewhere, but because the reading thread reads from its CPU core's cache, which might be different from the writing thread's one. To ensure value change propagation across CPU cores, you need to use proper memory fences, and you neither can nor need to use volatile for that in C++.