Why is retval a void** in pthread_join?

Viewed 317

I am having a hard time understanding why pthread_join's retval argument is a void**. I have read the manpage and tried to wrap my head around it but I still cannot fully understand it. I couldn't convince myself that retval cannot be a void*. Could someone please enlighten me?

Thank you very much in advance!

4 Answers

It's because you are supposed to supply the address of a void* to pthread_join. pthread_join will then write the address supplied by pthread_exit(void*) into the variable (who's address you supplied).

Example scenario:

typedef struct {
    // members
} input_data;

typedef struct {
    // members
} output_data;

Starting thread side:

input_data id;
pthread_create(..., start_routine, &id);

void* start_routine(void *ptr) {
    input_data *id = ptr;
    output_data *od = malloc(sizeof *od);
    // use the input data `id`, populate the output data `od`.
    pthread_exit(od);
}

Joining side:

output_data *od;
pthread_join((void**) &od);
// use `od`
free(od);

Simple enough. The return value of thread func supplied to pthread_create is void*; pthread_join is supposed to return this value to caller.

It can not return this as a function return type (because it is already returning int to indicate the overall status of the call). The only other way as through out parameter.

And the way C does out paramters is by using a pointer to the actual type of the parameter - i.e. if you want to do int as an out parameter, the type of the argument would be int*. If your out parameter is void* (because this is what you are returning from pthread func!), the type of the argument becomes void**.

As an exercise, you can try to write a similar code yourself - first, create a function which returns void* (say, void* foo()), and than try to write another function which would call foo() and communicate result back to the caller.

The exiting thread is going to provide a pointer to some data. The pthread routines do not know what type that data has, so they receive the pointer as a void *.

The caller of pthread_join is going to receive that void *. Since the function return value is used for something else, the void * has to be received through a parameter. So the caller has to pass a pointer to where pthread_join will put the void *. That pointer is a pointer to a void *, which is a void **.

From the manpage:

If retval is not NULL, then pthread_join() copies the exit status of the target thread (i.e., the value that the target thread supplied to pthread_exit(3)) into the location pointed to by retval.

Let's look at the signature of pthread_exit.

noreturn void pthread_exit(void *retval);

So that means if we wanted to return an int from our thread it would look something like this:

void* foo() {
    // ...
    int value = 255;
    pthread_exit(&value);
}

This works because the compiler doesn't care that it's an int* or a void*, either way it's a pointer of the same size.

Now we want to actually extract the return value of the thread using pthread_join.

void bar() {
    pthread_t thread_id;
    int *returnValue;
    // create thread etc...

    // the original type of returnValue was an `int*` so when we pass it in
    // with "&" it's now become `int**`
    pthread_join(thread_id, &returnValue);

    printf("%d\n", *returnValue); // should print 255
}

In plain English pthread_join takes a pointer and sets it address to point at the retval from your thread. It's a void** because we need the address of the pointer to be able to set the underlying pointer to what we want.

Related