6 ms·
Despite that it was written by Alexandrescu, I can say that without a doubt this 2001 article doesn't represent the current state of thought around MT programmi
by BeeOnRope 7y ago
Despite that it was written by Alexandrescu, I can say that without a doubt this 2001 article doesn't represent the current state of thought around MT programming and volatile. I'd think of it more as a historical artifact than anything.
- mehrdadn 7y agoThe fact that it doesn't doesn't mean it shouldn't be. It's a damn useful method, it just didn't become popular.
- BeeOnRope 7y agoIt 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.
- mehrdadn 7y agoIt 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.
- BeeOnRope 7y agoYes 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.
- mehrdadn 7y ago> it is UB to define something as volatile Can you point me to which part of the standard says that it's UB to cast away a volatile reference to a non-volatile object? See my example in [1] if you don't see why the object itself doesn't need to be volatile. > it's weird No, you're just not used to it. It's perfectly fine once you use it a bit. And regardless, there's quite a huge chasm between "it's completely wrong and undefined behavior" and "I don't like it, it's weird". > why not just escapsulate the object itself inside a class that only hands out access under control of a lock? That's a separate discussion. Right now we need to get the UB-ness claims out of the way. Once we agree it's correct in the first place then we can discuss whether it looks "weird" or what its use cases might be. [1] https://news.ycombinator.com/item?id=20430882 https://news.ycombinator.com/item?id=20430882
- BeeOnRope 7y ago> Can you point me to which part of the standard says that it's UB to cast away a volatile reference to a non-volatile object? That is not UB, it's only UB if the object was defined volatile, which is what the article does, explicitly: > You should define objects that are shared between threads as volatile and never use const_cast with them — always use LockingPtr automatic objects. Let's illustrate this with an example [Example goes on to define the object volatile] > No, you're just not used to it. It's perfectly fine once you use it a bit. And regardless, there's quite a huge chasm between "it's completely wrong and undefined behavior" and "I don't like it, it's weird". There might be a glimmer of something interesting in overloading the use of volatile on user-defined types as a second type of access control analogous to "const" but that you use for some other purpose, e.g., gating access to functions based on their tread-safety, or anything else really. This article doesn't make a convincing case for it because the first example is UB, the second example is UB, it propagates the broken notion that volatile is useful for concurrent access to primitive types, it doesn't include any discussion of modern techniques like std::atomic<>, etc. Of course, that's no fault of the author, who wrote it 2001 when the well-defined way of doing things was 10 years away. It's mostly a problem when people try to promote this, today, as an insightful view on volatile and multithreaded code. As a whole, it isn't and propagates various falsehoods that people have been trying to get rid of forever. What glimmer of an interesting point is in there regarding using volatile-qualified objects as a second level of access control orthogonal to const is washed out by the other problems. > That's a separate discussion. Right now we need to get the UB-ness claims out of the way. It's UB. Just admit that it's UB because the flag_ example does concurrent access to an object from different threads, at least one of which is a write, and the LockingPtr and follow-on examples are UB because they involve casting away volatile from a volatile-defined object. If you can agree with that, then maybe you can present a related technique, different to the one in the article, which uses volatile in a useful way.
- ChrisLomont 7y agoIt’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.
- mehrdadn 7y agoIt 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; }