Using C/Pthreads: do shared variables need to be volatile?

前端 未结 13 791
迷失自我
迷失自我 2020-11-28 06:03

In the C programming language and Pthreads as the threading library; do variables/structures that are shared between threads need to be declared as volatile? Assuming that t

相关标签:
13条回答
  • 2020-11-28 06:14

    As long as you are using locks to control access to the variable, you do not need volatile on it. In fact, if you're putting volatile on any variable you're probably already wrong.

    https://software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/

    0 讨论(0)
  • 2020-11-28 06:15

    The underlying reason is that the C language semantic is based upon a single-threaded abstract machine. And the compiler is within its own right to transform the program as long as the program's 'observable behaviors' on the abstract machine stay unchanged. It can merge adjacent or overlapping memory accesses, redo a memory access multiple times (upon register spilling for example), or simply discard a memory access, if it thinks the program's behaviors, when executed in a single thread, doesn't change. Therefore as you may suspect, the behaviors do change if the program is actually supposed to be executing in a multi-threaded way.

    As Paul Mckenney pointed out in a famous Linux kernel document:

    It _must_not_ be assumed that the compiler will do what you want with memory references that are not protected by READ_ONCE() and WRITE_ONCE(). Without them, the compiler is within its rights to do all sorts of "creative" transformations, which are covered in the COMPILER BARRIER section.

    READ_ONCE() and WRITE_ONCE() are defined as volatile casts on referenced variables. Thus:

    int y;
    int x = READ_ONCE(y);
    

    is equivalent to:

    int y;
    int x = *(volatile int *)&y;
    

    So, unless you make a 'volatile' access, you are not assured that the access happens exactly once, no matter what synchronization mechanism you are using. Calling an external function (pthread_mutex_lock for example) may force the compiler do memory accesses to global variables. But this happens only when the compiler fails to figure out whether the external function changes these global variables or not. Modern compilers employing sophisticated inter-procedure analysis and link-time optimization make this trick simply useless.

    In summary, you should mark variables shared by multiple threads volatile or access them using volatile casts.


    As Paul McKenney has also pointed out:

    I have seen the glint in their eyes when they discuss optimization techniques that you would not want your children to know about!


    But see what happens to C11/C++11.

    0 讨论(0)
  • 2020-11-28 06:18

    Variables that are shared among threads should be declared 'volatile'. This tells the compiler that when one thread writes to such variables, the write should be to memory (as opposed to a register).

    0 讨论(0)
  • 2020-11-28 06:22

    POSIX 7 guarantees that functions such as pthread_lock also synchronize memory

    https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_11 "4.12 Memory Synchronization" says:

    The following functions synchronize memory with respect to other threads:

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

    Therefore if your variable is guarded between pthread_mutex_lock and pthread_mutex_unlock then it does not need further synchronization as you might attempt to provide with volatile.

    Related questions:

    • Does guarding a variable with a pthread mutex guarantee it's also not cached?
    • Does pthread_mutex_lock contains memory fence instruction?
    0 讨论(0)
  • 2020-11-28 06:23

    Volatile means that we have to go to memory to get or set this value. If you don't set volatile, the compiled code might store the data in a register for a long time.

    What this means is that you should mark variables that you share between threads as volatile so that you don't have situations where one thread starts modifying the value but doesn't write its result before a second thread comes along and tries to read the value.

    Volatile is a compiler hint that disables certain optimizations. The output assembly of the compiler might have been safe without it but you should always use it for shared values.

    This is especially important if you are NOT using the expensive thread sync objects provided by your system - you might for example have a data structure where you can keep it valid with a series of atomic changes. Many stacks that do not allocate memory are examples of such data structures, because you can add a value to the stack then move the end pointer or remove a value from the stack after moving the end pointer. When implementing such a structure, volatile becomes crucial to ensure that your atomic instructions are actually atomic.

    0 讨论(0)
  • 2020-11-28 06:24

    In my experience, no; you just have to properly mutex yourself when you write to those values, or structure your program such that the threads will stop before they need to access data that depends on another thread's actions. My project, x264, uses this method; threads share an enormous amount of data but the vast majority of it doesn't need mutexes because its either read-only or a thread will wait for the data to become available and finalized before it needs to access it.

    Now, if you have many threads that are all heavily interleaved in their operations (they depend on each others' output on a very fine-grained level), this may be a lot harder--in fact, in such a case I'd consider revisiting the threading model to see if it can possibly be done more cleanly with more separation between threads.

    0 讨论(0)
提交回复
热议问题