c++ volatile multithreading variables

2019-06-20 01:45发布

问题:

I'm writing a C++ app.

I have a class variable that more than one thread is writing to.

In C++, anything that can be modified without the compiler "realizing" that it's being changed needs to be marked volatile right? So if my code is multi threaded, and one thread may write to a var while another reads from it, do I need to mark the var volaltile?

[I don't have a race condition since I'm relying on writes to ints being atomic]

Thanks!

回答1:

C++ hasn't yet any provision for multithreading. In practice, volatile doesn't do what you mean (it has been designed for memory adressed hardware and while the two issues are similar they are different enough that volatile doesn't do the right thing -- note that volatile has been used in other language for usages in mt contexts).

So if you want to write an object in one thread and read it in another, you'll have to use synchronization features your implementation needs when it needs them. For the one I know of, volatile play no role in that.

FYI, the next standard will take MT into account, and volatile will play no role in that. So that won't change. You'll just have standard defined conditions in which synchronization is needed and standard defined way of achieving them.



回答2:

Yes, volatile is the absolute minimum you'll need. It ensures that the code generator won't generate code that stores the variable in a register and always performs reads and writes from/to memory. Most code generators can provide atomicity guarantees on variables that have the same size as the native CPU word, they'll ensure the memory address is aligned so that the variable cannot straddle a cache-line boundary.

That is however not a very strong contract on modern multi-core CPUs. Volatile does not promise that another thread that runs on another core can see updates to the variable. That requires a memory barrier, usually an instruction that flushes the CPU cache. If you don't provide a barrier, the thread will in effect keep running until such a flush occurs naturally. That will eventually happen, the thread scheduler is bound to provide one. That can take milliseconds.

Once you've taken care of details like this, you'll eventually have re-invented a condition variable (aka event) that isn't likely to be any faster than the one provided by a threading library. Or as well tested. Don't invent your own, threading is hard enough to get right, you don't need the FUD of not being sure that the very basic primitives are solid.



回答3:

volatile instruct the compiler not to optimize upon "intuition" of a variable value or usage since it could be optimize "from the outside".

volatile won't provide any synchronization however and your assumption of writes to int being atomic are all but realistic!

I'd guess we'd need to see some usage to know if volatile is needed in your case (or check the behavior of your program) or more importantly if you see some sort of synchronization.



回答4:

I think that volatile only really applies to reading, especially reading memory-mapped I/O registers.

It can be used to tell the compiler to not assume that once it has read from a memory location that the value won't change:

while (*p)
{
  // ...
}

In the above code, if *p is not written to within the loop, the compiler might decide to move the read outside the loop, more like this:

cached_p=*p
while (cached_p)
{
  // ...
}

If p is a pointer to a memory-mapped I/O port, you would want the first version where the port is checked before the loop is entered every time.

If p is a pointer to memory in a multi-threaded app, you're still not guaranteed that writes are atomic.



回答5:

Without locking you may still get 'impossible' re-orderings done by the compiler or processor. And there's no guarantee that writes to ints are atomic.

It would be better to use proper locking.



回答6:

Volatile will solve your problem, ie. it will guarantee consistency among all the caches of the system. However it will be inefficiency since it will update the variable in memory for each R or W access. You might concider using a memory barrier, only whenever it is needed, instead. If you are working with or gcc/icc have look on sync built-ins : http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html

EDIT (mostly about pm100 comment): I understand that my beliefs are not a reference so I found something to quote :)

The volatile keyword was devised to prevent compiler optimizations that might render code incorrect in the presence of certain asynchronous events. For example, if you declare a primitive variable as volatile, the compiler is not permitted to cache it in a register

From Dr Dobb's

More interesting :

Volatile fields are linearizable. Reading a volatile field is like acquiring a lock; the working memory is invalidated and the volatile field's current value is reread from memory. Writing a volatile field is like releasing a lock : the volatile field is immediately written back to memory. (this is all about consistency, not about atomicity)

from The Art of multiprocessor programming, Maurice Herlihy & Nir Shavit

Lock contains memory synchronization code, if you don't lock, you must do something and using volatile keyword is probably the simplest thing you can do (even if it was designed for external devices with memory binded to the address space, it's not the point here)



标签: c++ volatile