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