Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should i specify volatile keyword for every object that shares its memory between different threads

I just read Do not use volatile as a synchronization primitive article on CERT site and noticed that a compiler can theoretically optimize the following code in the way that it'll store a flag variable in the registers instead of modifying actual memory shared between different threads:

bool flag = false;//Not declaring as {{volatile}} is wrong. But even by declaring {{volatile}} this code is still erroneous
void test() {
  while (!flag) {
    Sleep(1000); // sleeps for 1000 milliseconds
  }
}
void Wakeup() {
  flag = true;
}
void debit(int amount){
   test();
   account_balance -= amount;//We think it is safe to go inside the critical section
}

Am I right?

Is it true that I need to use volatile keyword for every object in my program that shares its memory between different threads? Not because it does some kind of synchronization for me (I need to use mutexes or any other synchronization primitives to accomplish such task anyway) but just because of the fact that a compiler can possibly optimize my code and store all shared variables in the registers so other threads will never get updated values?

like image 277
FrozenHeart Avatar asked Jan 18 '26 15:01

FrozenHeart


2 Answers

It's not just about storing them in the registers, there are all sorts of levels of caching between the shared main memory and the CPU. Much of that caching is per CPU-core so any change made there will not be seen by other cores for a long time (or potentially if other cores are modifying the same memory then those changes may be lost completely).

There are no guarantees about how that caching will behave and even if something is true for current processors it may well not be true for older processors or for the next generation of processors. In order to write safe multi threading code you need to do it properly. The easiest way is to use the libraries and tools provided in order to do so. Trying to do it yourself using low level primitives like volatile is a very hard thing involving a lot of in-depth knowledge.

like image 124
Tim B Avatar answered Jan 21 '26 08:01

Tim B


It is actually very simple, but confusing at the same time. On a high level, there are two optimization entities at play when you write C++ code - compiler and CPU. And within compiler, there are two major optimization techniue in regards to variable access - omitting variable access even if written in the code and moving other instructions around this particular variable access.

In particular, following example demonstrates those two techniques:

int k; bool flag;

void foo() {
    flag = true;
    int i = k;
    k++;
    k = i;
    flag = false;
}

In the code provided, compiler is free to skip first modification of flag - leaving only final assignment to false; and completely remove any modifications to k. If you make k volatile, you will require compiler to preserve all access to k = it will be incremented, and than original value put back. If you make flag volatile as well, both assignments first to true, than two false will remain in the code. However, reordering would still be possible, and the effective code might look like

void foo() {
    flag = true;
    flag = false;
    int i = k;
    k++;
    k = i;
}

This will have unpleasant effect if another thread would be expecting flag to indicate if k is being modified now.

One of the way to achive the desired effect would be to define both variables as atomic. This would prevent compiler from both optimizations, ensuring code executed will be the same as code written. Note that atomic is, in effect, a volatile+ - it does all the volatile does + more.

Another thing to notice is that compiler optimizations are, indeed, a very powerful and desired tool. One should not impede them just for the fun of it, so atomicity should be used only when it is required.

like image 43
SergeyA Avatar answered Jan 21 '26 06:01

SergeyA