Search code examples
cmultithreading

Revisiting `volatile` in C while multithreading


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.


Solution

  • 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 marked volatile to prevent the compiler from optimizing out reads to A?

    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);