341008
341008

Reputation: 10232

volatile variable being optimized away?

I have a background thread which loops on a state variable done. When I want to stop the thread I set the variable done to true. But apparently this variable is never set. I understand that the compiler might optimize it away so I have marked done volatile. But that seems not to have any effect. Note, I am not worried about race conditions so I have not made it atomic or used any synchronization constructs. How do I get the thread to not skip testing the variable at every iteration? Or is the problem something else entirely? done is initially false.

struct SomeObject
{
    volatile bool done_;
    void DoRun();
};

static void RunLoop(void* arg)
{
   if (!arg)
      return;

   SomeObject* thiz = static_cast<SomeObject*>(arg);
   while( !(thiz->done_) ) {
      thiz->DoRun();
   }
   return;
}

Upvotes: 0

Views: 925

Answers (2)

johnnycrash
johnnycrash

Reputation: 5344

This works like you expect in msvc 2010. If I remove the volatile it loops forever. If I leave the volatile it works. This is because microsoft treats volatile like you expect, which is different than iso.

This works too:

struct CDone {
    bool m_fDone;
};

int ThreadProc(volatile CDone *pDone) {
}

Here is what MSDN says:

http://msdn.microsoft.com/en-us/library/12a04hfd.aspx

Objects that are declared as volatile are not used in certain optimizations because their values can change at any time. The system always reads the current value of a volatile object when it is requested, even if a previous instruction asked for a value from the same object.

Also, the value of the object is written immediately on assignment.

ISO Compliant:

If you are familiar with the C# volatile keyword, or familiar with the behavior of volatile in earlier versions of Visual C++, be aware that the C++11 ISO Standard volatile keyword is different and is supported in Visual Studio when the /volatile:iso compiler option is specified. (For ARM, it's specified by default). The volatile keyword in C++11 ISO Standard code is to be used only for hardware access; do not use it for inter-thread communication. For inter-thread communication, use mechanisms such as std::atomic from theC++ Standard Template Library.

Upvotes: 1

Jeff
Jeff

Reputation: 3525

volatile doesn't have any multi-threaded meaning in C++. It is a holdover from C, used as a modifier for sig_atomic_t flags touched by signal handlers and for access to memory mapped devices. There is no language-mandated compulsion for a C++ function to re-access memory, which leads to the race condition (reader never bothering to check twice as an "optimization") that others note above.

Use std::atomic (from C++11 or newer).

It can be, and usually is lock-free:

struct SomeObject {
  std::atomic_bool done_;
  void DoRun();
  bool IsDone() { return done_.load(); }
  void KillMe() { done_.store(true); }
};

static void RunLoop(void *arg) {
  SomeObject &t = static_cast<SomeObject &>(*arg);

  cout << t.done_.is_lock_free(); // some archaic platforms may be false

  while (!t.IsDone()) {
    t.DoRun();
  }
}

The load() and store() methods force the compiler to, at the least, check the memory location at every iteration. For x86[_64], the cache line for the SomeObject instance's done_ member will be cached and checked locally with no lock or even atomic/locked memory reads as-is. If you were doing something more complicated than a one-way flag set, you'd need to consider using something like explicit memory fences, etc.

Pre-C++11 has no multi-threaded memory model, so you will have to rely on a third-party library with special compiler privileges like pthreads or use compiler-specific functionality to get the equivalent.

Upvotes: 3

Related Questions