Thread Safety Using the Volatile Keyword
As we all know, to write thread-safe code, we need to use locks. Besides this, we have another problem: what if the computer optimizes the code that accesses a variable at a memory address, and break the thread-safety the locks provide? This is possible. See the following example:
enter_critical_section(&lock);
(*p)++;
leave_critical_section(&lock);
What if the compiler generates (pseudo) code like this:
mov | lock, ax |
push | ax |
call | enter_critical_section |
pop | ax |
mov | lock, ax |
push | ax |
call | leave_critical_section |
pop | ax |
mov | [p], ax |
inc | ax |
mov | ax, [p] |
I guess all of you would feel surprised. Yes. This is not all-right for the compiler to do, since the compiler doesn't know whether `leave_critical_section' uses pointer `p' or not. But consider, if `leave_critical_section' is implemented as an inline function or a macro, would the compiler know this? It's still difficult for the compiler to know because `p' may point to a global variable or a heap memory block, etc., but it may also point to a local variable that may be used by `leave_critical_section'.
Then, can we say, if we use a pointer here, we are safe to operate? Let me think it over to see whether we have proof on this. If the compiler simply ignores where the pointer points to, it cannot move the position of the code above. The result must be committed before any following statements.
Another thing is, when you use a global variable, never assume it to be safe, because `leave_critical_section' may be inline, and the compiler can safely change the position of (*p)++! The compiler just guarantees the correctness of mono-thread operation, but it doesn't guarantee the correctness of multi-thread operation.
Do we need to use volatile pointers? No. We don't. Because we can guarantee every remote memory address assignment (`*p' assignment) in the critical sections be committed immediately (or the compiler should know where `p' points to) and function calls in the critical section be done when they return.
However, for safety, what if the compiler can determine where `p' points to? Oh, then use a volatile pointer. In addition, do we need to make function calls in the critical section use volatile pointers? If the compiler is TOO smart, we must! In a case, when a compiler sees this:
p = &g_var1; /* a global variable */
*p = 2;
It may assume that `p' is always pointing to the variable. To protect against this, we should use a volatile pointer.
Fortunately, in modern compilers, the memory barrier is a supported pseudo instruction. The lock/unlock functions in the library contains that instruction, so it is safe to use non-volatile variables/pointers, as long as you use the lock/unlock functions.