The race condition that is avoided is illustrated by this code, which is very similar to the original code, but subtly different, and the difference makes it incorrect:
/* Incorrect code! */
#include <pthread.h>
#include <stdio.h>
static void *foo(void *vargp)
{
int id = *(int *)vargp;
printf("Thread %d\n", id);
return 0;
}
int main(void)
{
pthread_t tid[2];
int i;
for (i = 0; i < 2; i++)
pthread_create(&tid[i], NULL, foo, &i); // Bad idea!
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
return 0;
}
Since the function foo
takes a void *
argument, it seems logical to pass the address of the int
to it. However, this has a major problem:
- There is no guarantee which order the threads will execute, nor when, so there's no way to know which values the threads will see.
Indeed, when I ran this code the first time, both threads reported 2
.
The way around this is to not pass the address of i
but to pass i
by value. However, the argument is still supposed to be a void *
, so the code casts i
to a void *
before calling pthread_create()
, and the thread function undoes the cast to retrieve the value.
When I'm doing this, I also use <stdint.h>
to make the uintptr_t
type available, and I use:
int id = (uintptr_t)vargp;
and
pthread_create(&tid[i], NULL, foo, (void *)(uintptr_t)i);
That looks excessive and/or obsessive, but the uintptr_t
cast ensures the integer is the same size as a pointer to avoid the 'cast to pointer from integer of different size
' compiler warning (which, since I tell the compiler to treat all warnings as errors, is necessary for me to get the code to compile at all).
If you do pass a pointer-to-data to the thread function (foo
in this discussion), you must ensure that each thread you create gets its own copy of the data unless that data is meant to be identical in each thread.
You can see this technique at work in POSIX threads — unique execution.