Recently on this list, Paul referred to an "atomic integer swap" and an "atomic pointer swap." This was a new concept to me (and possibly others), and this e-mail shares what I've learned.

If you access a variable from multiple threads -- even a built-in variable like 'int', it is important to control access to the variable with a mutex, semaphore, or an atomic operation.

Alexander Sandler, on his blog, wrote a couple of good articles on the subject:

    "Do you need a mutex to protect an int?"
    http://www.alexonlinux.com/do-you-need-mutex-to-protect-int

    "Multithreaded simple data type access and atomic
    variables"
    
http://www.alexonlinux.com/multithreaded-simple-data-type-access-and-atomic-variables

The first article contains code that calculates a wrong answer on multiprocessor machines. I've attached a similar example that will even fail on a single-processor machine.

There is a wealth of reading material on using Mutexes and Semaphores. However, information on atomic operations appears to be sparse and hard-to-follow. So, here's what I've found:

    + At the moment, there is no built-in support in
      C/C++ for atomic operations.  You will need to use
      a library, compiler extension, or write your own
      in assembly code.

    + The GCC compiler has the built-in __sync_*()
      functions[1] that provide atomic operations.
      Note that the attached example is using this.

    + glib provides the g_atomic_*() functions[2].

    + Qt 4 has the q_atomic_*() functions.[3]  While
      they are accessible, they are /not/ a part of
      their stable, public API.

    + The next version of ISO C++ (code name c++0x)
      is expected to have support for atomic operations
      (E.g. the std::atomic<T> template) and memory
      barriers.  It may even require that all built-in
      types be atomic.

    + In the x86 instruction set, these are usually
      implemented using the 'LOCK' instruction prefix.[5]

When using atomic operations, perhaps the best advice I found is near the end of Sandler's second article:

    "When using atomic variables, some extra
    precautions have to be taken....  There is nothing
    that prevents you from incrementing value of the
    atomic variable with __sync_fetch_and_add() as I
    just demonstrated and later in the code doing same
    thing with regular ++ operator.

    "To address this problem, I strongly suggest
    wrapping around atomic functions and variables with
    either ADT in C or C++ class."[4]

Peace,
Gabriel

[1] http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html
[2] http://www.gtk.org/api/2.6/glib/glib-Atomic-Operations.html
[3] http://doc.trolltech.com/4.3/atomic-operations.html
    See also the Qt header file QtCore/qatomic_i386.h, and
    its brothers.
[4] 
http://www.alexonlinux.com/multithreaded-simple-data-type-access-and-atomic-variables#precautions
[5] http://siyobik.info/index.php?module=x86&id=159
/*********************************************************************
 * atomic_fail.c - Atomic operations demonstration
 *
 * This program demonstrates the importance of using mutexes,
 * semaphores, and atomic operations in multithreaded programs.
 *
 * The program will increment two global variables (allocated on the
 * heap).  One will be incremented using atomic operations.  The other
 * will not be.  The non-atomic variable will result in an incorrect
 * answer, EVEN ON A SINGLE-PROCESSOR MACHINE.
 *
 * If the global variable is an int (rather than a pointer-to-int),
 * the program will calculate the correct value on a single processor
 * machine.  On a multi-processor machine, the non-atomic variable
 * will still have a wrong answer.[1]
 *
 * This program requires:
 *    + The GCC compiler because it uses its __sync_*()
 *      extensions.
 *    + A processor that is supported with the GCC
 *      __sync_*() extensions.  (If your isn't supported,
 *      you should get a compile-time error.)
 *    + pthreads library.
 *
 * Build and run this program like this:
 *
 *    $ gcc -O0 -o atomic_fail atomic_fail.c -lpthread
 *    $ ./atomic_fail
 *
 *    Starting values for global ints:
 *    atomic = 0 nonatomic = 0
 *
 *    Final values:
 *    atomic = 20000000 nonatomic = 18011884
 *
 *    Expected values (for both): 20000000
 *
 * Make sure that you use -O0, otherwise GCC might optimize all of our
 * increments into (*nonatomic) += ops !!
 *
 * Gabriel M. Beddingfield, 2009-Dec-12
 * This program is placed in the public domain.
 *
 * [1] However, I don't have a multiprocessor machine to test with! :)
 *********************************************************************
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <assert.h>

/* Each thread will increment the global variable
 * 'ops' times.
 */
const unsigned long ops = 10000000UL;

/* The integer (*atomic) will be incremented using
 * atomic operations.
 *
 * The integer (*nonatomic) will be incremented using
 * the normal (non-atomic) increment operator.
 */
unsigned long *atomic;
unsigned long *nonatomic;

/* This is thread uses atomic operations
 */
void* thread_atomic(void* arg)
{
    unsigned long n;
    for(n=ops; n>0 ; --n) {
	__sync_fetch_and_add(atomic, 1);
    }
    return 0;
}

/* This thread does NOT use atomic operations.
 */
void* thread_nonatomic(void* arg)
{
    unsigned long n;
    for(n=ops; n>0 ; --n) {
	++(*nonatomic);
    }
    return 0;
}

int main(void)
{
    pthread_t atomic_t1, atomic_t2;
    pthread_t nonatomic_t1, nonatomic_t2;
    int rv;

    atomic = (unsigned long*)malloc(sizeof(unsigned long));
    *atomic = 0;
    nonatomic = (unsigned long*)malloc(sizeof(unsigned long));
    *nonatomic = 0;

    printf("\n");
    printf("Starting values for global ints:\n");
    printf("atomic = %lu nonatomic = %lu\n", *atomic, *nonatomic);

    /* Spawn and run 4 threads.  2 for the atomic ops, 2 for
     * non-atomic.
     */
    rv = pthread_create(&atomic_t1, 0, thread_atomic, 0);
    assert(rv == 0);
    rv = pthread_create(&atomic_t2, 0, thread_atomic, 0);
    assert(rv == 0);
    rv = pthread_create(&nonatomic_t1, 0, thread_nonatomic, 0);
    assert(rv == 0);
    rv = pthread_create(&nonatomic_t2, 0, thread_nonatomic, 0);
    assert(rv == 0);

    rv = pthread_join(atomic_t1, 0);
    assert(rv == 0);
    rv = pthread_join(atomic_t2, 0);
    assert(rv == 0);
    rv = pthread_join(nonatomic_t1, 0);
    assert(rv == 0);
    rv = pthread_join(nonatomic_t2, 0);
    assert(rv == 0);

    printf("\nFinal values:\n");
    printf("atomic = %lu nonatomic = %lu\n", *atomic, *nonatomic);
    printf("\nExpected values (for both): %lu\n\n", ops*2);

    return 0;
}
_______________________________________________
Linux-audio-dev mailing list
[email protected]
http://lists.linuxaudio.org/mailman/listinfo/linux-audio-dev

Reply via email to