Re: About volatile qualifier

Tech-Archive recommends: Repair Windows Errors & Optimize Windows Performance



On Wed, 18 Jun 2008 17:37:09 +0300, "Kürþat" <xx@xxxxxx> wrote:

Hi all,

In an article about volatile qualifier there is a small code by which the
author explains a hidden bug which occurs if we neglect using volatile
qualifier. The code is simple :

class Gadget
{
public:
void Wait() { while (!flag) Sleep(1000); }
void Wakeup() { flag = true;}
private:
bool flag;
};

The author says :

"If we neglect to qualify the variable "flag" as "volatile" then the
compiler optimizes access to that variable by caching it in a register. This
is a good optimization in single threaded environments. No need to read
value of the flag every time. But in multithreaded environmens there is a
surprise. If you call Wait from Thread-1 and Wakeup from Thread-2 then the
Thread-1 will never get the actual value of the flag and loop forever."

As far as know, for this happen this two threads should run on different
processors or cores because of cache issues. Does this happen on a single
core? If yes what is the reason?

The author is talking about code optimization, not caching, and he's
oversimplified the issue. In order for the compiler to treat flag as a
constant in Wait, it would have to be able to prove that the Gadget object
in question isn't reachable from the Sleep function, such that Sleep cannot
call Wakeup and modify flag. As Sleep is an opaque function residing in a
system DLL, this is typically very difficult to prove in real programs, and
it's not even a multithreading issue; it's a re-entrancy issue that is
relevant in single-threaded programs. The code is probably "safe" (but see
below) without volatile in real programs. Indeed, compiling with -O2 in VC9
demonstrates that the compiler reads flag on every iteration for a function
such as:

void f(Gadget& x)
{
x.Wait();
}

You would have to write a function like this for the compiler to treat flag
as constant:

void g()
{
Gadget x;
x.Wait();
}

More generally, the compiler would have to prove that the function "f" is
equivalent to "g" in terms of the reachability of x from Sleep, and in real
programs, that is, programs in which calling Wait doesn't mean going into
an infinite loop, this is typically very difficult to prove, at best. The
seemingly simple function "f" above is enough to make the VC9 compiler give
up, because it doesn't know the complete usage of the object bound to the
reference x.

However, this code may not be not safe in a multithreading environment due
to its violation of memory visibility rules. Now this does have something
to do with caching, and the general answer is to guard access to variables
such as flag with a mutex. In VC2005 and later, qualifying the variable
with volatile actually does something besides inhibiting compiler
optimizations on the variable; it also implies the use of memory barriers
on multiprocessor architectures that need them. This is totally
non-standard, and AFAIK, it's still an uncommon enhancement to volatile. As
I like to say, it mainly helps buggy, badly designed code continue to kinda
sorta work even on advanced multiprocessor systems, typically a lot slower
than necessary due to the frequent MB instructions.

--
Doug Harrison
Visual C++ MVP
.



Relevant Pages

  • Re: mutex question
    ... volatile bool flag = false; ... Here, you may need to use volatile to ensure that even though the compiler doesn't see 'flag' ever changing during the execution of the thread1 function, that it may still be changed by something external that the compiler can't see. ... To avoid that for sure, you need appropriate memory barriers, which will prevent both the compiler and the CPU from reordering the write. ...
    (microsoft.public.win32.programmer.kernel)
  • Re: About volatile qualifier
    ... If "compilation" given by Alex is what the MS compiler actually does, ... But if you set the flag inside the loop, it will work even without volatile. ... If there is something inside the while body that is obscure for the compiler, then "caching" optimization is not enabled. ...
    (microsoft.public.vc.language)
  • Re: Newbie question: accessing global variable on multiprocessor
    ... the case that the compiler will optimize away operations on a non- ... volatile global variable that a. ... void function_wait_for flag() ...
    (comp.lang.c)
  • Re: why are some atomic_ts not volatile, while most are?
    ... your compiler is reordering instructions that have side effects which are invisible to the compiler. ... where flag is an atomic_t that is set in an interrupt handler, the volatile may be necessary on some architectures to force the compiler to re-read "flag" each time through the loop. ... Without the "volatile", the compiler could be perfectly within its rights to evaluate "flag" once and create an infinite loop. ...
    (Linux-Kernel)
  • Re: Is the following code MT-Safe?
    ... the assert (which is a fundamental design error: ... and almost always leads to either major synchronization failures ... >Does _bRunning need to be tagged as volatile? ... compiler to cache values, but only during the execution of a function; ...
    (microsoft.public.vc.mfc)