2

Sadly I'm stuck with C++98, which I'm using in an embedded application.

My question is: I have a multithreaded application, with various global shared variables (evil, I know). I do protect every access to them using mutexes. Do I also need to declare these global variables as volatile, in order to prevent the compiler from optimizing accesses to them?

Searching online it seems that volatile is absolutely useless for multithreading, but a lot of articles are related to C++11, which did introduce a memory model which recognizes threads, but I'm in C++98 land. I also found some resources that indicate that volatile is instead useful in my case, such as this Barr Group's article.

Let me emphasize the fact that I don't want to get rid of the mutexes at all, or try lock free programming. The mutexes are absolutely staying, I just want to understand if the volatile keyword is needed.

fraben
  • 98
  • 3
  • 10
  • Use mutexes. volatile doesn't protect you from race conditions – bobra Nov 14 '20 at 00:25
  • @bobra My reading of their question is that they understand that. – PiRocks Nov 14 '20 at 00:26
  • It's not the race condition I'm worried about, but the compiler optimization. – fraben Nov 14 '20 at 00:26
  • https://stackoverflow.com/questions/50951011/how-does-a-mutex-lock-and-unlock-functions-prevents-cpu-reordering states that mutexes protect you from cpu reorderings, and https://stackoverflow.com/questions/11172922/does-stdmutex-create-a-fence weakly suggests you are protected from compiler reorderings. – PiRocks Nov 14 '20 at 00:27
  • @fraben compiler is not that bad that you expect :D volatile enforces your program in read/write this variable in memory instead of using cpu cache. – bobra Nov 14 '20 at 00:30
  • also volatile enforces to create variable and handle every it's operation even if it's no used actually – bobra Nov 14 '20 at 00:31
  • to avoid compiler optimizations you can put the global in an `extern` function: `extern int myglobal() { static int the_global = 0; return the_global; }` – GreatAndPowerfulOz Nov 14 '20 at 00:48
  • @GreatAndPowerfulOz There are compilers that have no difficulty optimizing globals accessed in `extern` functions. Relying on what you think the compiler isn't smart enough to do is a *bad* idea that has caused many, many bugs. – David Schwartz Nov 14 '20 at 02:02
  • @DavidSchwartz You may have a good point. This is just a starting point. I've used this technique many-a-time without issue, but of course, I always put such methods in separate compilation units and export them from the object file. So, yeah, it's possible to prevent optimization - by the compiler. – GreatAndPowerfulOz Nov 14 '20 at 05:25

2 Answers2

2

Do I also need to declare these global variables as volatile, in order to prevent the compiler from optimizing accesses to them?

No. And if you did, you would still be in trouble because volatile is not sufficient -- things other than the compiler (such as the CPU, posting buffers, and memory controllers) can also optimize accesses.

As I'm sure you've read elsewhere, volatile has no defined multi-threading semantics in C++98. So unless it does in your particular threading standard (which you don't specify), then it's completely useless to you.

Presumably, your code uses mutexes properly. No optimization is allowed to break code that only relies on guarantees provided by the relevant standards or implementation. So if you're using the mutexes correctly, then your code is guaranteed to work.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • Thanks for the answer :) I really don't expect any particular multithreading semantics from volatile. What worries me is that the mutex isn't tied to the variable it is protecting in any way, so the compiler might try to optimize things. I read this, for example, at https://betterembsw.blogspot.com/2014/06/minimize-use-of-global-variables.html?m=1. In the comments section, the author of the post basically says what I just wrote. – fraben Nov 14 '20 at 08:46
  • 2
    @fraben You are worried about the wrong thing. The compiler is not the only thing in your system allowed to optimize code. *Everything* in the system is allowed to optimize code including the CPU, memory controller, and so on. The best way to protect yourself is to use mutexes correctly. Nothing is allowed to break correct code. Unless your threading library documentation says that `volatile` does something in particular with threads, it's a huge mistake to expect it to do anything in particular. – David Schwartz Nov 14 '20 at 22:33
0

What does keyword volatile mean ?

You enforce your program always to read/write your variable to memory (like cache miss every time). Always (CPU->L1->L2->L3->Bus->Memoryand Memory->Bus->L3->L2->L1->CPU) Actually it slows your program, so you should not use it until exact need.

You may know that compiler may do some optimizations, but these are designed not to affect/change your program logic.

Example 1:

int d = 5; 
int b = 10;
for (int i=0; i < 1e9; i++){
    cout << i;
    b++;
}
cout << d; // d may be created only here, or not created at all. compiler may just cout << 5;
      // var b - seems to be skipped at all due to being unused

Example 2:

int a = 0;
for (int i = 0; i < 10; i++){
    a++;
    sleep(1000);
}
cout << a;

// this code could be compiled like the following one
sleep(10000); 
cout << 10;

Keyword volatile prevents your var from such optimizations. i.e. vars a, b will be created and incremented, also cache missed always.

Here is one of the most common use cases for using volatile:

You have a third party sensor or scoreboard, that is attached to var address, and its value is always read and shown on the scoreboard. so in this case you need volatile, to prevent compiler optimizations on it, so your score board can show correct value.

I guess now you can decide whether you need volatile or not

bobra
  • 615
  • 3
  • 18