This guideline has not been reviewed recently and may be outdated. Please review it and comment to reflect any newly available information.
There is the misconception that the
volatile qualifier will provide the desired properties of a multithreaded program, which are:
- Atomicity: indivisible memory operations
- Visibility: the effects of a write action by a thread are visible by other threads
- Ordering: Sequences of memory operations by a thread are guaranteed to be seen in the same order by other threads
volatile does not provide guarantees for any these. Neither by definition nor by the way it is implemented in various platforms.
The following is the standard's definition of
C++ standard definition
p.34 SS1.9Accessing an object designated by a volatile lvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression might produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.
p.138volatile 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...... In general, the semantics of volatile are intended to be the same in C + + as they are in C.
p.25Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects,11) which are changes in the state of the execution environment. Evaluation of an expression may produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.
In a nutshell, all this keyword does is to inform the compiler that this variable may change in ways that cannot be determined by the compiler and so not to perform optimizations in these memory areas marked as
volatile, i.e., not to store the value in a register and use the register instead of doing expensive memory accesses. This concept is much related to multithreading, since if a shared variable is cached, a thread may change it and the other threads may read stale data. Although earlier C++ standards did not address thread issues, the latest describes how the library should support threads. In the case of dangerous optimizations such as these, the C++ standard notes:
ISO/IEC JTC 1/SC 22 N 4411p. 27 of 1314
"Compiler transformations that introduce assignments to a potentially shared memory location that would not be modified by the abstract machine are generally precluded by this standard, since such an assignment might overwrite another assignment by a different thread incases in which an abstract machine execution would not have encountered a data race. This includes implementations of data member assignment that overwrite adjacent members in separate memory locations. We also generally preclude reordering of atomic loads in cases in which the atomics in question may alias, since this may violate the "visible sequence" rules."
Noncompliant Code Example
In the following example, if the flag is declared as
volatile, the flag still should not be used as a syncronization primitive in order to modify an account balance.
The above noncompliant example is similar to an example described by Alexandrescu in his article Volatile: A programmer's best friend. Although this article is very good, the initial example is similar to the above and causes confusion to the multithreading novice.
Someone would note that if the variable flag is not declared
volatile, then if we compile with optimizations on, the compiler may cache the flag in a register, and as a result the thread that sleeps may never escape the while loop because the change of the flag to true by another thread will not be reflected in the register (the registers and stack are not shared among threads, so if the memory contents are not updated a thread may read the old value).
The problem with this thinking is not realizing that by changing the declaration of the flag to
volatile bool flag we only ensure that the compiler generates code to reload a data item each time it is referenced in our program. Although the problem of reading staled data is solved, the above example is not the correct way to implement multithreading, since the
volatile keyword does not guarantee for all the attributes required for safe multithreading.
Firstly by declaring a variable as
volatile although the
volatile qualifier guarantees that the reads and writes will happen in the exact order specified in the source code, the compiler may generate code which reorders a
volatile read or write with non-
volatile reads or writes, thus limiting its usefulness as an interthread flag or mutex.
Noncompliant Code Example
Let's assume that this trivial code is shared among threads and
done is a variable that when set indicates that the thread has finished updating the shared buffer.
In this example, the compiler can change the ordering, and as a result we may first have the assignment 1 to the variable
done and then the update of message. This is of course solved by declaring buffer to be
volatile as well (and maybe all variables in the program just to be safe???), but that is beside the point. Moreover, it is not guaranteed that
volatile reads and writes will be seen in the same order by other processors due to caching, meaning
volatile variables may not even work as interthread flags or mutexes.
Concerning the use of
volatile, David Butenhof, the author of Programming with POSIX threads, says:
"Don't ever use the C/C++ language volatile in threaded code. It'll kill your performance, and the language definition has nothing to do with what you want when writing threaded code that shares data."
In fact Andrei Alexandrescu, in his article Volatile: A programmer's best friend," states a different opinion, more moderate than Butenhof's, and gives an example of a LockingPtr which shows that the semantics of
volatile do not apply inside a critical section and cast_const's away the volatileness and notes:
"Don't forget: volatile equals free multithreaded code and no critical section; non-volatile equals single-threaded scenario or inside a critical section."
Most of the aforementioned properties on multithreading are not related to standards but are more hardware features (e.g., atomicity). And none of these properties are consistent with the semantics of
volatile. For example, in order to achieve correct ordering, memory barriers would be required - something completely irrelevant with the notion of
volatile. C\C++ standards until recently did not address multithreading, and each vendor implemented its own version (under Windows we can use WIN32 threads, under Linux we can use POSIX threads, under Solaris the pthread API implementation, etc.). So the correct way to implement multithreaded programs and avoid the undesirable side effects of race conditions would be to use a library or language extension that implements the atomic and memory fence semantics. An example of compliant code would be the same as the one offered in POS00-C. Avoid race conditions with multiple threads.
Compliant Solutions (For Example 1)
The compliant solutions for the previous noncompliant examples:
Compliant Solutions (For Example 2)
In the compliant solution the synchronization among threads is not through the Boolean
done, but race conditions are avoided by acquiring mutexes.
This rule can be found in the C Secure Coding Standard as CON02-C. Do not use volatile as a synchronization primitive.
C++ Standard ISO/IEC 14882
C++ standard ISO/IEC JTC 1/SC 22 N 4411
Article by Andrei Alexandrescu: Volatile - Multithreaded Programmer's Best Friend
David Butenhof: Programming with POSIX threads, comp.programming.threads
The Volatile Keyword