2

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!

lumapools
  • 64
  • 6

4 Answers4

3

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);
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Why wouldn't it work if `pthread_join` was implemented expecting a `void*` as argument? Couldn't we supply the address directly as a void*, without going through a double pointer? What would be wrong with that implementation? – lumapools Aug 11 '21 at 17:41
  • @nullpoin7er It would then have an address to some data. It could not store the address of the data it's supposed to return from the thread. I added a small scenario to the answer. – Ted Lyngmo Aug 11 '21 at 17:43
  • 1
    I now understand what the source of my confusion was. I didn't look enough into how `pthread_exit` works, but with your example it makes perfect sense. Many thanks! – lumapools Aug 11 '21 at 17:57
  • 1
    @nullpoin7er You're very welcome! Glad it helped! – Ted Lyngmo Aug 11 '21 at 17:58
1

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.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
1

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 **.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
1

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.

John Doe
  • 111
  • 2
  • 14