← Back to context

Comment by mehrdadn

7 years ago

The fact that it doesn't doesn't mean it shouldn't be. It's a damn useful method, it just didn't become popular.

It’s completely broken. One of the modern Meyers books even has a chapter on not using volatile in the Dr. Dobbs article manner.

When the article was written, there was no real alternative, and volatile accidentally worked nicely on certain architectures. It failed on others. It absolutely was never designed to do what you’re trying to defend. It’s always been non-portable, implementation and architecture behavior on how it handled memory read/write barriers. Now that there’s proper ways to do barriers portably, the volatile approach is terrible advice.

C++ 11 addressed this all in a proper manner, after much research and many papers on the matter. Since then, for major compilers on major architectures, the new C++11 features have been implemented correctly. Volatile has zero use for correct multi threading code. It only has use for memory mapped hardware from a single properly synchronized thread.

Your article, as people keep telling you but you seem unable to accept it, is wrong. It’s now absolutely not portable, it’s inherently broken, and leads to undefined, hard to debug, terrible behavior for threading issues.

Go dig up the backstory on how C++11 got its threading model and dig up the More Effective C++ chapter on it to learn why your article is bad.

  • It sounds like you don't get what the article's point is. The article is NOT using volatile as a barrier mechanism. It's using it as a compiler-enforced type annotation, which you strip away before accessing the variable of interest. It sounds like absolutely nobody here is willing to read the article because they think they already know everything the article could possibly be saying. Fine, I give up, you win. I've summarized it here for you.

    The idea is this you can use volatile like below. It's pretty self-explanatory. Now can you look through this code and tell me where you see such a horrifying lack of memory barriers and undefined behavior? (And don't point out something irrelevant like how I didn't delete the copy constructor.)

      #include <mutex>
    
      template<class T>
      class lock_ptr
      {
          T *p;
      public:
          ~lock_ptr() { this->p->m.unlock(); }
          lock_ptr(volatile T *p) : p(const_cast<T *>(p)) { this->p->m.lock(); }
          T *operator->() const { return p; }
      };
    
      class MyClass
      {
          int x;
      public:
          MyClass() : x() { }
          mutable std::mutex m;
          void bar() { ++x; }
          void foo() volatile { return lock_ptr<MyClass>(this)->bar(); }
      };
    
      void worker(volatile MyClass *p)  // called in multiple threads
      {
          p->foo();  // thread-safe, and compiles fine
          p->bar();  // thread-unsafe, and compile-time error
      }
    
      #include <future>
    
      int main()
      {
          MyClass c;
          auto a = std::async(worker, &c);
          auto b = std::async(worker, &c);
          a.wait();
          b.wait();
          return c.x;
      }

    • > It sounds like you don’t get what the articles point is.

      Yes I do. It’s simply wrong. What it says about type annotation is correct, but has zero to do with threading because volatile has zero meaning for accesses from different threads. It then uses volatile to (incorrectly) build threading code. You seem to think volatile has some usefulness for threaded code; it does not. You think volatile adds benefit to your code above; it does not. The type annotation does not give you the ability to have compilers check race conditions for you - it works on some and will fail on others.

      Add volatile to your bar function. Oops, got race conditions. Volatile is not protecting your code; properly using mutexes is. Requiring programmers to intersperse volatile as some type annotation makes code more error prone, not less. One still has to correctly do the hard parts, but now with added confusion, verbosity, and treading on undefined behavior.

      I think you believe his claim “We can make the compiler check race conditions for us.” because you’re relying on the same claim compilers will check volatile in the manner your code above does. That’s undefined behavior, open to compiler whims. Good luck with that. There’s a reason C++ added the more nuanced ordering specifications - to handle the myriad ways some architectures worked (and to mirror discoveries made in academic literature on the topic that happened after this article was written).

      This article is even mentioned in the proposal to remove volatile from C++ altogether http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p115.... I’ve known about this for some time, and hacking in type annotations like this adds no value; it simply makes a mess.

      More errors from the article, which is why people should stop citing it:

      First sentence:

      “The volatile keyword was devised to prevent compiler optimizations that might render code incorrect in the presence of certain asynchronous events.”

      This is simply wrong. The article goes on to try to make multithreaded code correct using volatile.

      More quotes from the article that are simply wrong: “Although both C and C++ Standards are conspicuously silent when it comes to threads, they do make a little concession to multithreading, in the form of the volatile keyword.” Wrong; see Sutter quote below. “Just like its better-known counterpart const, volatile is a type modifier. It's intended to be used in conjunction with variables that are accessed and modified in different threads.” Wrong. See Sutter quote, and ISO standards. Volatile was never intended for this, so was never safe for doing this. “In spite of its simplicity, LockingPtr is a very useful aid in writing correct multithreaded code. You should define objects that are shared between threads as volatile” wrong on so many levels. The referenced code will break on many, many architectures. There is simply no defense to this.

      The article has dozens more incorrect statements and code samples trying to make threadsafe code via volatile.

      I’ve written articles on this. I’ve taught professional programmers this. I’ve designed high performance C++ multithreaded code for quite a while. It’s simply wrong, full stop.

      Here’s a correct destruction of the Dobbs article by someone who gets it [1]. They, like you, were once misled by this article.

      The money quote, from Herb Sutter “Please remember this: Standard ISO C/C++ volatile is useless for multithreaded programming. No argument otherwise holds water; at best the code may appear to work on some compilers/platforms”

      I suspect you’ll still stick to the claim this article has value, given your insistence so far against so many people giving you correct advice. Good luck.

      [1] https://sites.google.com/site/kjellhedstrom2/stay-away-from-...

      4 replies →

It shouldn't be.

The stuff the article recommends is straight up UB in modern C++. Volatile has never been specified to work properly with threads, but before C++11 when there was no alternative, some limited use in that context, preferably hidden away from the casual user, may have been acceptable. Recommending these techniques today, however, makes no sense.

  • It should be.

    The stuff you're taking about is not the same stuff I'm talking about. There's nothing UB about the locking pointer pattern and how it uses volatile. Read the article in full. It has a specific thesis that is just as valid today as it was 20 years ago, and that thesis is NOT the 2001 malpractice you're talking about.

    • Yes the locking pointer pattern shown there is also UB because it is UB to define something as volatile and then cast away the volatile and use it, which is the core of that technique.

      Yes, it's not UB in the race sense, because he is using mutxes everywhere there and just sort of overloading the volatile qualifier to catch member function calls outside the lock. In addition to being UB, it's weird - why not just escapsulate the object itself inside a class that only hands out access under control of a lock? That is, why have the volatile object passed in from the outside if you will never legally access the object?

      The very premise of this article, that volatile is for concurrently modified objects across threads is false in modern C++ - and the very first example is a faulty use of volatile under the assumption that unguarded concurrent volatile access is safe.

      5 replies →