Thread: Do I need a mutex if I'm only sharing an int between threads?

  1. #1
    Registered User
    Join Date
    Jun 2009
    Posts
    101

    Do I need a mutex if I'm only sharing an int between threads?

    I have a multithreaded app with a thread that needs to access an int in the main thread that provides status information (e.g. playing or paused). This int may be set and read by either thread. Do I need a mutex around it if it's only an int? I understand the need for a mutex if say, a data buffer needs to be filled/read, but if it's only an integer value, do I still need the mutex?

    Is it really possible that the integer could be "half-read" at some point, causing an error?

  2. #2
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Before I go into any of this, let me say up front that "volatile" is NOT the solution. In case anybody wants to claim that.

    If the type is atomic (which int usually is, provided it is properly aligned in memory), then you do not strictly need a mutex to protect access to it. However, there are pitfalls if you don't use one. The biggest is the fact that the compiler may cache the value of the variable in a register, causing a thread to not see updates made to the variable.

    In order to get the compiler to flush the variable to memory, you need to access it through functions instead of accessing it directly:

    Code:
    atomic flag; // make sure the type is atomic
    
    int ReadFlag() { return flag; }
    void WriteFlag(int value) { flag = value; }
    By wrapping the access in a function call, the compiler is forced to flush/reload the global from memory.

    Here's the thing though. ANY function call will flush the variable -- it doesn't necessarily even have to do with the shared memory. So, if you need to call a function before reading the variable and after writing it, to ensure it goes back to memory, you might as well make that function be a mutex locking function and thus you get two kinds of correctness: the value is guaranteed not to be stale; and you are using a locking mechanism instead of relying on atomic memory access.

    A well-implemented mutex should be very lightweight, if there isn't much contention on it (which in your application it sounds like there isn't).

    EDIT: There's also the matter of memory coherence, but for multi-valued updates, that CAN'T be implemented without either locking or memory barriers (and the skill to do it right), so I left that part out.
    Last edited by brewbuck; 10-11-2012 at 06:09 PM.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  3. #3
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    If you use GCC or the Intel Compiler Collection, then you can use the atomic built-in functions for this. The C++11-style builtin atomic operations will probably be standardized in a decade or so, but until they become more widely available, I'm sticking with the legacy-style __sync ones. (I think also Pathscale and Portland Group C compilers provide these, but I'm too lazy to check, sorry.)

    Note that you need to use the atomic built-ins for all accesses to the variable; it is not enough to do it atomically on one side. (The reason being that the non-atomic side can do the read/write in two separate parts, with the atomic access in between.)

    For example, to obtain the value of the integer variable atomically, I use
    Code:
    value = __sync_fetch_and_or(&variable, 0);
    Clearing is similarly trivial (as is setting or removing individual bits from the variable). This clears the variable, but returns the value it had:
    Code:
    old = __sync_fetch_and_and(&variable, 0);
    The link shows the instructions to add to or substract from the variable, and so on.

    Setting the variable to a fixed value is a bit more complicated. I've found the best option to be the compare-and-swap loop. Usually it does not loop at all:
    Code:
    /* Set variable = value, saving old value in old */
    do {
        old = variable;
    } while (!__sync_bool_compare_and_swap(&variable, old, value));
    If you are wondering, the access in the body is not atomic, and it does not need to be. The atomic call compares the current value of variable to the old value atomically, only changing it (in the same atomic operation) to the new value value if it matches. If the access in the body of the loop did not match, no harm done: the atomic call will just return 0 (false), causing another iteration of the loop.

    The compare-and-swap can be used to access floats and doubles and pointers atomically too, on most architectures:
    Code:
    if (sizeof (variable) == sizeof (unsigned long) {
        do {
            old = variable;
    
            /* Note: you can recompute new value here, using old */
    
        } while (!__sync_bool_compare_and_swap((unsigned long *)&variable, (unsigned long)old, (unsigned long)value);
    
    } else
    if (sizeof (variable) == sizeof (unsigned int) {
        do {
            old = variable;
    
            /* Note: you can recompute new value here, using old */
    
        } while (!__sync_bool_compare_and_swap((unsigned int *)&variable, (unsigned int)old, (unsigned int)value);
    
    } else {
        /* Not supported! */
    }
    while just getting the value is simpler:
    Code:
    if (sizeof (variable) == sizeof (unsigned long))
        *((unsigned long)&variable) = __sync_fetch_and_or((unsigned long)&variable, 0UL);
    else
    if (sizeof (variable) == sizeof (unsigned int))
        *((unsigned int)&variable) = __sync_fetch_and_or((unsigned int)&variable, 0U);
    else
        /* Not supported! */
    Because normal Linux architectures are all either ILP32 (32-bit) or LP64 (64-bit), the above has always worked for me in Linux, on any architecture I've tried it on. I've successfully used the above to manipulate both pointers and floating-point values atomically.

    More complicated structures can be read atomically, if they have two generation counters. When modified, one generation counter is increased first. After modifying the structure contents, the second generation counter is also increased. (Memory write barriers or __sync_synchronize() may be needed after increasing the first counter and before increasing the second counter, to make sure the stores are not reordered over the atomic increments.) Any reader will simply read the first generation counter first, then copy the structure contents, and then the second counter. If the two counters mismatch, the read operation must be redone. (Memory read barriers may be needed; I tend to treat both read and modify the same.)

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Mutex and Multiple Threads Problem
    By roweboats in forum C++ Programming
    Replies: 8
    Last Post: 06-21-2010, 03:04 PM
  2. Replies: 2
    Last Post: 05-08-2008, 09:50 AM
  3. Sharing sockets among threads
    By Hawkin in forum Networking/Device Communication
    Replies: 19
    Last Post: 02-04-2008, 04:13 PM
  4. a point of threads to create multiple threads
    By v3dant in forum C Programming
    Replies: 3
    Last Post: 10-06-2004, 09:48 AM
  5. Producer-comsumer threads using semaphore mutex
    By Kaiya in forum C Programming
    Replies: 3
    Last Post: 04-29-2004, 08:17 AM

Tags for this Thread