0

I am trying to understand following code. This code has no race conditions, but I cannot understand.

#include <stdio.h>
#include <pthread.h>

void *foo(void *vargp) {
    int id;
    id = (int)vargp;
    printf("Thread %d\n", id);
}

int main() {
    pthread_t tid[2];
    int i;

    for (i = 0; i < 2; i++)
        pthread_create(&tid[i], NULL, foo, (void *)i);
    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);
    return 0;
}

How does the typecasting from int to void* work?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Kavar Rushikesh
  • 143
  • 1
  • 10

1 Answers1

1

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.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278