4

Notice!

I'm obviously failing to make my point clearly to everyone here, and it's incredibly frustrating. My goal was to dispel the myth that volatile is effectively a no-op, that it does nothing. I was not trying to state that it should be used, that it is essential, that it is not redundant, etc.

I have shown that volatile does still do a thing. I concede that it is redundant under certain circumstances, and that the multi-threaded example was a poor choice.

I am also not trying to hide the fact that my answer's initial revisions contained errors. But this Q&A is not even coming close to fulfilling its intended purpose. To that end, I think it's time to chuck it away.

Thanks to Kerrek and T.C. for their insights. I just don't think their responses fit the question that I wanted to ask. I'm sure that's my fault for asking it poorly.

Therefore I am abandoning it! And closing it as a duplicate of the question not that it was intended as, but that it has been interpreted as.

Cheers! (& hth.)

I am writing to a variable in one thread and reading from it in another. I have been told that volatile is completely useless for this and that I do not need to use it in this day and age unless I am working with hardware.

int x = 0;
void thread1()
{
   while (true) {
      sleep(1);
      if (x > 0)
         break;
   }
}

void thread2()
{
   while (true) {
      sleep(1);
      x++;
   }
}

Do I gain anything by using volatile in this case?
And how about if x is not a simple int but a class type?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    **Note:** this question was prompted by some heated debate. There are a few questions on the general topic of `volatile`, but I felt that none really specifically attacked the misconception that `volatile` has no place in modern multi-threaded code. However, I have referenced two partially-relevant extant questions in my answer. – Lightness Races in Orbit Mar 17 '15 at 19:24
  • 1
    @LightnessRacesinOrbit Of course `volatile` _"**has it's place** in modern multi-threaded code"_! Even such code at least needs to interact with guaranteed non optimized out assumptions made by the compiler. Be it hardware behavior, or lock-free implementations of something. – πάντα ῥεῖ Mar 17 '15 at 19:30
  • @πάνταῥεῖ Yes, that's what my long answer says! – Lightness Races in Orbit Mar 17 '15 at 19:31
  • @LightnessRacesinOrbit Just giving the question a lance :) ... – πάντα ῥεῖ Mar 17 '15 at 19:32
  • A useful use of `volatile` is `volatile asm("" : : "memory")`. – Kerrek SB Mar 17 '15 at 19:34
  • 3
    As a side note, `volatile` is typically used with peripheral registers which can go and change on their own. Your code might need, say, a spin loop like `while(STATUS & 0x80);` (where `STATUS` is something like `volatile uint8_t& STATUS{*reinterpret_cast(0x40F00010)};` and you don't want it optimized into `if(STATUS & 0x80) while(1);` which is what happens if `STATUS` is not `volatile` and the compiler decides to read it once into a register and then never fetch it again. Fun with drivers. – Mike DeSimone Mar 17 '15 at 19:37
  • I've never encountered this myth either. Maybe my experience is out of date, but the last I've read much about `volatile`, it was still common for people to use it to facilitate communication between threads and experts were trying to push the knowledge that `volatile` really isn't the way to go about doing so. –  Mar 17 '15 at 20:07
  • 2
    I've never encountered a myth that it does nothing. The only myth I've encountered is that it can be used to avoid data races between threads. Portably, it can't (although on some platforms that will be the case), and trying to do so just gives more subtle undefined behaviour. (In this case, it could fail if `x` briefly takes an invalid value during modification, or if the change to one thread's view of memory never propagates to the other's.) – Mike Seymour Mar 17 '15 at 20:12
  • 2
    http://isvolatileusefulwiththreads.com/ (click on C++ and there are several authoritative references) – Jonathan Wakely Mar 18 '15 at 09:57

1 Answers1

20

You have been told correctly, volatile is not useful for inter-thread communication. Don't use it for that purpose. It does not provide synchronization and leaves your code with data races. Conversely, when synchronizing access to shared state correctly, you do not need volatile.

The correct code that Does What You Mean uses atomic variables for the shared state or protects the shared state with a mutex, and the shared state will be observed correctly by all threads. For example:

#include <atomic>

std::atomic<int> x = 0;

void thread1()
{
   while (true) {
      sleep(1);
      if (x > 0)
         break;
   }
}

void thread2()
{
   while (true) {
      sleep(1);
      x++;
   }
}

At no point is there any need for volatile.

Note that volatile may be useful within a thread to enforce that a loop that otherwise has no side effects is evaluated:

// Spend some time
for (volatile int i = 0; i != LARGE_NUMBER; ++i)
{ /* do nothing */ }

// or even:
for (int i = 0; const_cast<volatile int &>(i) != LARGE_NUMBER; ++i) {}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • That's only possible in a narrow subset of cases w.r.t. the type of `x`. – Lightness Races in Orbit Mar 17 '15 at 19:30
  • @LightnessRacesinOrbit: Yes, fixed: `std::string x; std::mutex x_mutex;` – Kerrek SB Mar 17 '15 at 19:32
  • Your answer still says `std::atomic x`. An `std::atomic` cannot wrap a `std::string`. I think you might be missing the point of the question :/ Perhaps I can make it clearer. It's not "is `volatile` the best approach to solve this problem". It's "is it or is it not a myth that `volatile` literally doesn't do anything". – Lightness Races in Orbit Mar 17 '15 at 19:33
  • I've tinkered with it a bit. For the record I don't disagree at all that `std::atomic` is what _should_ be used in the OP's specific code example. – Lightness Races in Orbit Mar 17 '15 at 19:36
  • @LightnessRacesinOrbit: Nobody ever claimed that "`volatile` does nothing" (e.g. see the standards references in the other answer). But the question explicitly says "variables that are shared across threads", which is incredibly misleading. – Kerrek SB Mar 17 '15 at 19:36
  • I've heard loads of people claim that, which is why I wrote this Q&A. People underestimate what `volatile` does, particularly those are not aware in the first place that such optimisations are even possible. – Lightness Races in Orbit Mar 17 '15 at 19:36
  • How is it "misleading"? – Lightness Races in Orbit Mar 17 '15 at 19:37
  • 2
    @LightnessRacesinOrbit: Because it's almost exactly reinforcing an extremely common and dangerous misconception. I almost can't believe that you posted this, to be honest. If you wanted to discuss the uses of `volatile`, I'd say a question like "Is volatile useless?", or "what are the effects of volatie?" would be far more helpful. Keep the multithreading stuff out of it in any case! – Kerrek SB Mar 17 '15 at 19:47
  • Perhaps you did not see the edits I made to the question to that exact effect over 15 minutes ago. There's no need to be rude :( I've been open about my goal here. And that goal _is_ to throw off the dangerous misconception. – Lightness Races in Orbit Mar 17 '15 at 19:49
  • 4
    @LightnessRacesinOrbit Except that you are adding a misconception. The question asked is about volatile access across threads, and you (or anyone else) has yet to post an example where they are necessary in this context. – Nevin Mar 17 '15 at 19:52
  • _"Don't use it for that purpose. It does not provide synchronization and leaves your code with data races."_ FFS I never claimed it provides synchronisation. I literally wrote all over this page that it does not provide synchronisation. That was the entire purpose of writing this Q&A, because everybody keeps falsely claiming that I'm claiming it provides synchronisation. Sigh. – Lightness Races in Orbit Mar 17 '15 at 19:52
  • @Nevin: Where did I claim that they are "necessary in this context"? Hint: nowhere. – Lightness Races in Orbit Mar 17 '15 at 19:52
  • 6
    @LightnessRacesinOrbit They aren't necessary for sharing across threads. They aren't sufficient for sharing across threads. So why put them in, other than as a complication and a pessimization? – Nevin Mar 17 '15 at 19:54
  • @Nevin: Everyone's missed the point completely, unfortunately. This is not a surprise, though. The reason for posting this Q&A was to serve as something I could link to when people kept misrepresenting my claims. I failed. – Lightness Races in Orbit Mar 17 '15 at 19:59
  • @KerrekSB: Thanks for your response - please see my notice on the question. I don't want you to think that I'm "hiding" from the Q&A now just because I made mistakes on it. But it's not fulfilling my intent in any way because everyone's focusing on the wrong aspect. I've concluded that I can't salvage this – Lightness Races in Orbit Mar 17 '15 at 20:00
  • This is fine for an atomic `int` but what about for protecting access to more complicated structures? (`std::atomic` can only be used with trivially copyable classes). – M.M Mar 17 '15 at 20:11
  • @Nevin: I did learn here that the use of `std::mutex` and friends affects the sequencing of reads/writes (beyond just physically blocking code execution). That is the key to this whole mess, I suppose. That is why my chosen example was totally inappropriate, and consequently why nobody's talking about `volatile` for `volatile`'s sake, but fixating on the fact that it turns out to be redundant (I consider this distinct from "does nothing") in well-synchronised, multi-threaded code. – Lightness Races in Orbit Mar 17 '15 at 20:13
  • 1
    My understanding is that `volatile` is necessary because without it, a change made by one thread may not be seen in another thread. By writing `std::atomic` you wrap the volatile-ness up inside a container.; but suppose you had something like `std::string s;` and you wanted to use that from multiple threads, you would have to mark it `volatile` (and also implement a mutex or similar to prevent concurrent access). Of course I could be wrong here but I'd appreciate information/discussion, rather than any thread about volatile being shut down with "you know nothing, go away". – M.M Mar 17 '15 at 20:15
  • 4
    @MattMcNabb No, you don't mark it `volatile`. You just write proper synchronization and that's sufficient. (Outside of ``, no standard library class has `volatile` member functions, so if you write a `volatile std::string` there's very little you can do with it.) – T.C. Mar 17 '15 at 20:34
  • 1
    @TC what prevents the non-volatile write being optimized away if it does not change the observable behaviour of the thread doing the write? – M.M Mar 17 '15 at 20:36
  • 3
    @MattMcNabb The synchronization operations. (Given write-unlock-lock-read, with the first two operations happening on one thread and the second two happening on a different thread, the unlock *synchronizes with* lock, so the write *happens before* the read, and assuming no intervening writes, by [intro.multithread]/p15 the read must read what's been written by the write.) – T.C. Mar 17 '15 at 20:43
  • 3
    @MattMcNabb: I would consider you a prime victim of this post. No, `volatile` is *not* necessary; the C++ memory model and consequent requirements on library implements guarantee that using a mutex provides correct visibility. – Kerrek SB Mar 17 '15 at 21:06
  • @LightnessRacesinOrbit: As I said, I'd be all in favour of a post along the lines of "what's the use of `volatile`"; your standards quote and the point about side effects and loops is a very interesting and often overlooked one. – Kerrek SB Mar 17 '15 at 21:07
  • 3
    There's another thing that prevents such a write being optimized away - the as-if rule in [intro.execution]/1. In order to use, let's say, a string variable from another thread, either you need to pass a pointer or reference to it from the translation unit where the write happens (in which case the implementation can see that and shouldn't assume writes do nothing) or the variable must be global and declared extern in another translation unit (which the implementation can't necessarily see), and in the extern case the implementation must prove the write isn't observable in order to optimize. – Ville Voutilainen Mar 17 '15 at 21:08
  • A Q&A "How do the C++11 synchronization rules render `volatile` unnecessary" would be useful . – M.M Mar 17 '15 at 21:10
  • 3
    Now, since the original code doesn't use an atomic or a volatile variable, we should focus our attention to [intro.multithread]/27: "The implementation may assume that any thread will eventually do one of the following: — terminate, — make a call to a library I/O function, — access or modify a volatile object, or — perform a synchronization operation or an atomic operation." The original code does none of these, so the implementation may act on that assumption. Making the supposedly-shared variable volatile doesn't make it work across threads, but would prevent acting on said assumption. – Ville Voutilainen Mar 17 '15 at 21:14
  • @MattMcNabb: I think that's a loaded question: It sounds like you're presuming that volatile *was* once necessary (for synchronisation?), but the synchronization rules are in some sense orthogonal to `volatile`. – Kerrek SB Mar 17 '15 at 21:14
  • @VilleVoutilainen the string could be a class member, and two of the class's member functions could be running in separate threads – M.M Mar 17 '15 at 21:14
  • @VilleVoutilainen: I believe that one of Herb Sutter's talks mentioned that there are reordering rules pertaining to `volatile`, but I can't find anything to that effect in the Standard. Does that ring a bell? – Kerrek SB Mar 17 '15 at 21:16
  • @VilleVoutilainen: /27 is great; I've referred to it countless times when people ask about code with loops. It's a fine paragraph (and I always forget that it's in 1.10). – Kerrek SB Mar 17 '15 at 21:17
  • 1
    @Kerrek: Once upon a time, common wisdom was that marking a variable `volatile` was the standard solution for defeating optimizations that would prevent inter-thread communication. A lot of ancient wisdom continues to be propagated. :( –  Mar 17 '15 at 21:18
  • @KerrekSB well, it was necessary (but not sufficient) for synchronization prior to C++11 when threads were implemented as a library ([see here](http://stackoverflow.com/a/6838005/1505939)) – M.M Mar 17 '15 at 21:20
  • 1
    @KerrekSB I suppose [intro.execution]/8: "The least requirements on a conforming implementation are: — Access to volatile objects are evaluated strictly according to the rules of the abstract machine." – Ville Voutilainen Mar 17 '15 at 21:27
  • @KerrekSB, you might be thinking of Herb's http://www.drdobbs.com/parallel/volatile-vs-volatile/212701484 article – Jonathan Wakely Mar 18 '15 at 10:03