I have read many questions about this topic here, but still feel like the point is being missed by many answers I read.
The Question: Should variables shared between threads in pure C be marked volatile
?
Disclaimer: I very much understand that volatile
does not mean atomic. That is, just because my variables are marked volatile
doesn't mean I don't need to worry about race conditions.
The Point: If I have a variable A
which is made atomic via the use of a mutex, shouldn't A
still be marked volatile
to prevent the compiler from optimizing out reads to A
?
Consider the following example:
mutex_t m;
static int flag = 0;
// Thread 1
void writer(void) {
lock(m)
flag = 1;
unlock(m);
}
// Thread 2
void reader(void) {
int read = 0;
while (1) {
lock(m);
read = flag;
unlock(m);
if (read) exit(0);
}
}
Given the functions writer
and reader
are executing in different threads, wouldn't it be possible for the compiler to optimize out the repeated reads to flag
inside the reader
function?
I feel like flag here should be made volatile
?
Final Disclaimer: I understand that there probably exists a nice atomic/thread-safe type which could be used for small integral values like flag
in this example. However, I am more asking this question for the situation where the data shared between two threads is a large struct.
Should variables shared between threads in pure C be marked
volatile
?
No, not unless they are in fact volatile
, meaning that the value of such a variable may change outside the program's control or that reads of the variable has side effects not known to the program.
shouldn't
A
still be markedvolatile
to prevent the compiler from optimizing out reads toA
?
Again, only if the value of A
can change outside the program's control or if reads of A
has side effects, like if it's connected to hardware that reacts to reads.
I feel like flag here should be made
volatile
?
It should not. You've protected reads and writes with a mutex so it's all fine. Making it volatile
could in fact have a negative impact on the program's performance since it would force the compiler to actually make the program read and write flag
from/to memory when it may not need to. Example:
lock(m);
printf("%d\n", flag);
// the below can use a cached value of the previous read of flag
// unless you make flag `volatile`:
printf("%d\n", flag);
unlock(m);