37

Consider a simple (global in my case) variable:

int i;

Somewhere this variable is accessed

pthread_mutex_lock(i_mutex);
if(i == other value) {
  do_something();
}
pthread_mutex_unlock(i_mutex);

Another thread updates i while it holds i_mutex . Could the compiler cache the value of i so I don't get the recent value ? Must i be volatile ?

Manuel Selva
  • 18,554
  • 22
  • 89
  • 134
Anonym
  • 7,345
  • 8
  • 35
  • 32

3 Answers3

41

pthread locks implement memory barriers that will ensure that cache effects are made visible to other threads. You don't need volatile to properly deal with the shared variable i if the accesses to the shared variable are protected by pthread mutexes.

from http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_11:

The following functions synchronize memory with respect to other threads:

fork()
pthread_barrier_wait()
pthread_cond_broadcast()
pthread_cond_signal()
pthread_cond_timedwait()
pthread_cond_wait()
pthread_create()
pthread_join()
pthread_mutex_lock()       // <====
pthread_mutex_timedlock()
pthread_mutex_trylock()
pthread_mutex_unlock()     // <====
pthread_spin_lock()
pthread_spin_trylock()
pthread_spin_unlock()
pthread_rwlock_rdlock()
pthread_rwlock_timedrdlock()
pthread_rwlock_timedwrlock()
pthread_rwlock_tryrdlock()
pthread_rwlock_trywrlock()
pthread_rwlock_unlock()
pthread_rwlock_wrlock()
sem_post()
sem_timedwait()
sem_trywait()
sem_wait()
semctl()
semop()
wait()
waitpid()
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • So, basically, a `for(;;) { int k; pthread_mutex_lock(i_mutex); k = i; if(k == other_value) do_something(); pthread_mutex_unlock(i_mutex); ` , the `i` would always be read from memory and never optimized into being kept in e.g. a register, just because the precence of pthread_mutex_XX ? – Anonym Jul 08 '10 at 21:47
  • 6
    @Anonym: No, it's not because of pthread_mutex_XX, it's because of the function call (a sequence point in C). See Jens Gustedt's answer. The memory barrier doesn't protect against caching, it protects against reordering (it doesn't let accesses cross the barrier). – ninjalj Jul 08 '10 at 21:53
  • 6
    @ninjalj: The function call sequence point *combined* with the memory barrier provided by the pthreads functions ensure that the consistent `i` value will be used. The sequence point alone is not enough because without the memory barrier cache effects might read a stale value of `i` even if the compiler inserts a memory read after the function call (for example if some non-pthread function were called). – Michael Burr Jul 08 '10 at 22:02
  • 1
    got it, the compiler assumes any of those function calls could modify `i` , and will have to re-read it - volatile or not, while the pthread_mutex_xxx ensures the low level consistency. – Anonym Jul 08 '10 at 22:04
  • 2
    @Anonym: Almost. The function call avoids caching and acts as a compiler barrier (prevents the compiler reordering the access acros the compiler barrier). Possibly a CPU barrier prevents the CPU from reordering the access across the CPU barrier. And the mutex prevents simultaneous access to the protected data. Or so goes my understanding. For the fine details, you definitely need a wizard, a simple guru won't do ;P – ninjalj Jul 08 '10 at 22:17
  • In the Java memory model, synchronizing on an object `o` only updates `o` with respect to other threads. Is this saying that in C++ *everything* is updated with respect to other threads? – Rag Sep 05 '13 at 21:42
  • @BrianGordon "_synchronizing on an object o only updates o with respect to other threads_" Could you be more specific? What is not updated? – curiousguy Nov 25 '19 at 19:17
  • @MichaelBurr "_might read a stale value of i even if the compiler inserts a memory read after the function call_" How exactly could that happen? – curiousguy Nov 25 '19 at 19:19
  • @curiousguy Any other variable in scope. – Rag Nov 28 '19 at 05:09
  • @BrianGordon I don't see how in Java a locally declared variable would be accessible by another thread. So of course they aren't affected by mutexes, as they are function private. – curiousguy Nov 28 '19 at 21:56
5

A compiler should not cache such a global value across a function call.

But I think your question is ill-posed. First, POSIX mutex only work when you stick to their semantics. So you have to apply some discipline in your code to only access global variables (i in that case) whence your mutex is hold.

Second, please don't think that volatile declarations would prevent you from any damage that such a non-respect of the access rules would cause. Concurrent read and write into memory is a subtle subject.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • 1
    This is correct, but the "discipline" you mention is simply that you must lock the mutex whenever your read *or* write the data you are interested in. – Randy Stegbauer Jul 08 '10 at 21:48
  • @Randy: sure, for some people such a simple discipline seems to be already difficult :-) Since you mention read and write, let me just note for the records that there also are POSIX read-write locks, that could perform a bit better if there are a concurrent readers. – Jens Gustedt Jul 09 '10 at 05:21
2

The simple answers to your question are:
- No, i will be the most recent value.
- No, i does not have to be volatile.

The i_mutex protects your access to i...as long as your lock the mutex every time you read and write it. Simple as that.

In other words, you don't have to worry about caching or volatile.

Enjoy, Randy

Randy Stegbauer
  • 1,104
  • 2
  • 11
  • 25