Thread: Use of "volatile" in multithreading

  1. #1
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229

    Use of "volatile" in multithreading

    I am using the Boost library, but I suppose it applies to other threading libraries, too.

    Is "volatile" needed for all resources shared between threads? Including mutexes?

    Thanks

    [edit]I am aware that volatile shouldn't be used for synchronization. Just for optimization suppression.[/edit]
    Last edited by cyberfish; 06-08-2009 at 06:40 AM.

  2. #2
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    No. "volatile" is not necessary.

    The only thing that is necessary is proper synchronization using the primitives proved by the threading library. Boost is built on top of Win32 and pthreads. Neither require volatile when using it's synchronization primitives.

    gg

  3. #3
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    But then what will be stopping the compiler from optimizing out accesses to the shared variable? or storing them in registers? If the code is run on multi-core processors (or multi-CPU), it would be bad to have them in registers?

    Another question -
    How do I make sure the compiler won't reorder assignments (optimization)?
    AFAIK, memory barriers only tells the CPU to not reorder assignments?

  4. #4
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    For inhibiting compiler reordering, Wikipedia says
    Memory barrier - Wikipedia, the free encyclopedia
    Memory barrier instructions only address reordering effects at the hardware level. Compilers may also reorder instructions as part of the program optimization process. Although the effects on parallel program behavior can be similar in both cases, in general it is necessary to take separate measures to inhibit compiler reordering optimizations for data that may be shared by multiple threads of execution. Note that such measures are usually only necessary for data which is not protected by synchronization primitives such as those discussed in the previous section.
    But I don't see why (should I add a "citation needed"? ).

  5. #5
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by cyberfish View Post
    But then what will be stopping the compiler from optimizing out accesses to the shared variable?
    That's what synchronisation is about. By ensuring that access to the shared variables are appropriately protected by a mutex, critical section, or semaphore (e.g. all threads lock the mutex before accessing the variables, and unlock the mutex after), you achieve a guarantee that no thread will attempt to modify a variable while another one is.

    Compilers, practically, are not aggressive enough to upset that synchronisation: either because mutexes are specially treated (see below for discussion on memory barriers), or because the instructions or function calls to manipulate them are opaque to the compiler.
    Quote Originally Posted by cyberfish View Post
    or storing them in registers? If the code is run on multi-core processors (or multi-CPU), it would be bad to have them in registers?
    Compilers would not generally place global data (which is shared across multiple threads) into registers - simply because that would not be an effective use of registers.

    Generally, if a compiler was somehow smart enough to use registers to share data between threads, it would be smart enough to ensure those registers are managed appropriately across threads. Practically, I'm aware of no compilers that even try to do this.

    Quote Originally Posted by cyberfish View Post
    Another question -
    How do I make sure the compiler won't reorder assignments (optimization)?
    AFAIK, memory barriers only tells the CPU to not reorder assignments?
    The short answer is that you don't. If you follow the practices of using synchronisation, the order of events between locking a mutex (or whatever synchronisation primitive) and unlocking it will be irrelevant - in terms of detectable effects, any reordered code will be indistinguishable from the original.

    If memory barrier instructions are needed for your CPU architecture, they will be used to implement the synchronisation primitives (mutexes, critical sections, etc). You will only really need to worry about using memory barriers if actually implementing an operating system (or if you are rewriting your compiler and library). Application code is unlikely to directly use such instructions.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  6. #6
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    But what about something like this -
    Code:
    Lock l;
    
    void f(int &ptr) {
         lock(l);
         *ptr = 5;
         unlock(l);
    }
    
    int main() {
         int a = 0;
         thread t(f, &a);
         sleep(1);
         lock(l);
         printf("%d", a);
         unlock(l);
    }
    What's stopping the compiler from optimizing out the reading from a in the printf line? Couldn't the compiler optimize it to "printf("0");"?
    Last edited by cyberfish; 06-08-2009 at 08:09 AM. Reason: fixed a typo

  7. #7
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    The short answer is that you don't. If you follow the practices of using synchronisation, the order of events between locking a mutex (or whatever synchronisation primitive) and unlocking it will be irrelevant - in terms of detectable effects, any reordered code will be indistinguishable from the original.
    Thanks, I think I got that.

  8. #8
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Btw, your example shouldn't compile because you're passing a pointer to the function but accepting a reference (why aren't you using references for that matter?).
    And why are you using printf instead of std::cout?
    As for the optimization question, unless the compiler can ensure that the value will not change, it will not optimize it. In other words, it will not optimize your scenario because of the boost::thread object.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  9. #9
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Quote Originally Posted by cyberfish View Post
    But what about something like this -
    Code:
    Lock l;
    
    void f(int &ptr) {
         lock(l);
         *ptr = 5;
         unlock(l);
    }
    
    int main() {
         int a = 0;
         thread t(f, &a);
         sleep(1);
         lock(l);
         printf("%d", a);
         unlock(l);
    }
    What's stopping the compiler from optimizing out the reading from a in the printf line? Couldn't the compiler optimize it to "printf("0");"?
    Syntax pointed out elsewhere aside:
    And if we assume that thread t is simply a function call that takes a pointer to int, would that also fail if you don't make int volatile?

    Now, if we added a while-loop around the lock/printf/unlock, and we change the thread to loop around constanly incrementing a, I would agree that there is a POSSIBILITY that the compiler may not realize that a may be changing at times.

    Of course, since there is no synchrnonization between the threads, there is also the problem that despite the sleep, the thread may not have run by the time it reaches the printf - there is no guarantee that a new thread runs ANY amount of time after the start of the thread [it's highly LIKELY that it will, but if the system is EXTREMELY BUSY, it may happen that it doesn't].

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  10. #10
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Basically, the moment you pass a pointer/reference to a variable to a function that the compiler cannot analyze perfectly, alias analysis will tell it that pretty much anything can happen to that variable. Especially if the the compiler knows the meaning of threads.

    If the compiler doesn't know threads, you can't use it for compiling multi-threaded programs. You simply cannot. It is far too unpredictable.

    Take a look at this BoostCon 2009 presentation to see an example of the horrors a thread-unaware compilers can inflict on you.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  11. #11
    Registered User
    Join Date
    Dec 2006
    Location
    Canada
    Posts
    3,229
    Btw, your example shouldn't compile because you're passing a pointer to the function but accepting a reference (why aren't you using references for that matter?).
    And why are you using printf instead of std::cout?
    Thanks, it's just a typo. This is meant to be pseudo-code.

    And if we assume that thread t is simply a function call that takes a pointer to int,
    This is the boost::thread (and std::thread) syntax. The constructor creates a thread with an entry point of the first parametre (the function), and pass the rest of the arguments to the function.

    would that also fail if you don't make int volatile?
    That is my question. Should I?

    Now, if we added a while-loop around the lock/printf/unlock, and we change the thread to loop around constanly incrementing a, I would agree that there is a POSSIBILITY that the compiler may not realize that a may be changing at times.
    What would be the difference between that and the case I posted?

    Of course, since there is no synchrnonization between the threads, there is also the problem that despite the sleep, the thread may not have run by the time it reaches the printf - there is no guarantee that a new thread runs ANY amount of time after the start of the thread [it's highly LIKELY that it will, but if the system is EXTREMELY BUSY, it may happen that it doesn't].
    Thanks, I am aware of that. It's quick and dirty pseudo-code.

    Basically, the moment you pass a pointer/reference to a variable to a function that the compiler cannot analyze perfectly, alias analysis will tell it that pretty much anything can happen to that variable. Especially if the the compiler knows the meaning of threads.

    If the compiler doesn't know threads, you can't use it for compiling multi-threaded programs. You simply cannot. It is far too unpredictable.

    Take a look at this BoostCon 2009 presentation to see an example of the horrors a thread-unaware compilers can inflict on you.
    I think that answered my question. So it would be like the compiler assumed it's volatile?

    Interesting read, too.

  12. #12
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    You can generally assume the following:

    1. All global variables are consistent in their state during a function call. This implies that a call to a mutex locking function or other synchro primitive will cause all global data which might currently be in registers to be dumped to memory. (Global data might be in registers because it is actively being manipulated.)

    2. If pointers to local variables escape the current scope, the compiler will assume that anything might happen to those variables, and ensure that their in-memory values are consistent across function calls (i.e, calls to synchro primitives)

    If you need synchronized access to a variable which does NOT fall into either of these categories (it's not global, or escaped local, or you are not calling any synchro primitive), you MIGHT need volatile. In general, no.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Multithreading (flag stopping a thread, ring buffer) volatile
    By ShwangShwing in forum C Programming
    Replies: 3
    Last Post: 05-19-2009, 07:27 AM
  2. multithreading in C++
    By manzoor in forum C++ Programming
    Replies: 19
    Last Post: 11-28-2008, 12:20 PM
  3. Question on Multithreading
    By ronan_40060 in forum C Programming
    Replies: 1
    Last Post: 08-23-2006, 07:58 AM
  4. Client/Server and Multithreading
    By osal in forum Windows Programming
    Replies: 2
    Last Post: 07-17-2004, 03:53 AM
  5. Multithreading
    By JaWiB in forum Game Programming
    Replies: 7
    Last Post: 08-24-2003, 09:28 PM