1

The question arises after reading some codes written by another developers, so I did some research and I found article by Andrei Alexandrescu. In his article he says that it is possible to use volatile boolean variable for busy waiting (see the first example with Wait/Wakeup)

class Gadget
{
public:
    void Wait()
    {
        while (!flag_)
        {
            Sleep(1000); // sleeps for 1000 milliseconds
        }
    }
    void Wakeup()
    {
        flag_ = true;
    }
    ...
private:
    bool flag_;
};

I don't really get how does it work.

  1. volatile doesn't guarantee that the operations will be atomic. Practically reads/writes to boolean variable are atomic, but theory doesn't guarantee that. From my point of view the code above could be safely rewritten with C++11 by using std::atomic::load/store functions with acquire/release memory ordering constraints correspondingly.
  2. We don't have this problem in the described example, but if we have more then one write we may have problems with memory ordering. Volatile is not a fence, it doesn't force memory ordering, it just prevents compiler optimization.

So why so many people use volatile bool for busy wait and is it really portable?

Cœur
  • 37,241
  • 25
  • 195
  • 267
axe
  • 2,331
  • 4
  • 31
  • 53
  • I am no authority in this area, but my instinct is the same as yours. Also it may be worth noting that there are no `volatile bool`s in your code ;) It also _may_ be relevant that the article dates back to 1st February 2001. – Lightness Races in Orbit Nov 12 '13 at 15:45
  • 1
    [Possible answer](http://stackoverflow.com/a/4168894/560648) on near-duplicate question. – Lightness Races in Orbit Nov 12 '13 at 15:49
  • Yes, you are right. I copied source code from the first example, he adds volatile later. Let me fix that. – axe Nov 12 '13 at 15:49
  • @Lightness Races in Orbit: Thanks for the link, looks interesting. I'll check it now. – axe Nov 12 '13 at 15:50

2 Answers2

2

The article doesn't say that volatile is all you need (indeed, it is not), only that it can be useful.

If you do this, and if you use the simple generic component LockingPtr, you can write thread-safe code and worry much less about race conditions, because the compiler will worry for you and will diligently point out the spots where you are wrong.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • If so then it's clear, however I was talking about this part `Most explanations of the rationale and usage of volatile stop here and advise you to volatile-qualify the primitive types that you use in multiple threads. However, there is much more you can do with volatile, because it is part of C++'s wonderful type system.` He doesn't say that people who advise to use volatile for primitive types used in multiple threads are wrong, because actually volatile has nothing to do with multithreading in C++. – axe Nov 12 '13 at 15:56
  • @axe: Well, what about it? `volatile` _is_ part of the type system, so you can choose overloads based on it. You have to look at it as _one tool_ among many in a possible design of a multi-threaded application. – Lightness Races in Orbit Nov 12 '13 at 15:57
  • Where did you take it from? :) – axe Nov 12 '13 at 15:58
  • @axe: Where did I take what from? – Lightness Races in Orbit Nov 12 '13 at 15:59
  • I mean did you copy that sentence? I thought it is from article, but couldn't find it there. – axe Nov 12 '13 at 16:13
  • @axe: It's from the summary at the end of the article, just after it says "Don't use `volatile` directly with primitive types." – Mike Seymour Nov 12 '13 at 16:15
  • @axe: It was a paraphrase, as it turns out, but my own thoughts and words. – Lightness Races in Orbit Nov 12 '13 at 17:28
1

I don't really get how does it work.

It relies on two assumptions:

  • reads and writes to boolean variables are atomic;
  • all threads have a uniform view of memory, so that modifications made on one thread will be visible to others within a short amount of time without an explicit memory barrier.

The first is likely to hold on any sane architecture. The second holds on any single-core architecture, and on the multi-core architectures in widespread use today, but there's no guarantee that it will continue to hold in the future.

the code above could be safely rewritten with C++11 by using std::atomic

Today, it can and should be. In 2001, when the article was written, not so much.

if we have more then one write we may have problems with memory ordering

Indeed. If this mechanism is used for synchronisation with other data, then we're relying on a third assumption: that modification order is preserved. Again, most popular processors give that behaviour, but there's no guarantee that this will continue.

why so many people use volatile bool for busy wait

Because they can't or won't change habits they formed before C++ acquired a multi-threaded memory model.

and is it really portable?

No. The C++11 memory model doesn't guarantee any of these assumptions, and there's a good chance that they will become impractical for future hardware to support, as the typical number of cores grows. volatile was never a solution for thread synchronisation, and that goes doubly now that the language does provide the correct solutions.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644