0

I just started learning multi-threading in C++ with pthreads. I am working with the following code:

struct ArgT{
    int a;
    int b;

    ArgT(int a, int b)
        : a(a), b(b)
    {}

    ArgT()
        :a(0), b(0)
    {}
};

void* worker(void* arg)
{
    ArgT* myArg = (ArgT*) arg;
    int* result = new int;

    *result = myArg->a + myArg->b;

    std::cout << "(void*)result: " << (void*) result << std::endl;
    return (void*)result;
}

int main()
{
    ArgT mainArg(2,3);
    pthread_t p;
    int* main_result;

    pthread_create(&p, NULL, worker, (void*)&mainArg);
    pthread_join(p, (void**)&main_result); //??

    std::cout << "main_result: " << main_result << std::endl; 
    std::cout << "&main_result: "<< &main_result << std::endl;
    printf("\nResult = %d\n", *main_result);

    delete main_result;

    return 0;
}

The output of the code is as follows

(void*)result: 0x7f07ac0008c0
main_result: 0x7f07ac0008c0
&main_result: 0x7fffb1aa0588

Result = 5

My question is pthread_join() accepts void** as second argument, which is basically address of an address. Whereas we are returning an address of type void* in the worker() function. How are these two types compatible?

HariP19
  • 29
  • 1
  • 6
  • 2
    Since you're programming C++ I really recommend you look into [`std::thread`](https://en.cppreference.com/w/cpp/thread/thread) instead. Or if you expect a result back [`std::async`](https://en.cppreference.com/w/cpp/thread/async). – Some programmer dude Dec 07 '20 at 07:42
  • As for the `void**` issue, it's a way to emulate pass by reference in C (the POSIX threads API is a C API). – Some programmer dude Dec 07 '20 at 07:43
  • Thanks for the input. That's my next question, what's the main advantage of using std::thread instead of pthreads. I am mainly sticking to pthreads because the book that I am currently reading(Operating Systems: Three Easy Pieces) explained everything with pthread @Someprogrammerdude – HariP19 Dec 07 '20 at 07:49
  • 1
    Besides it being part of the standard C++ library, and therefore portable? Well it's typically much easier. You could just have `void worker(ArgT argument) { ... }` and then create a thread like `std::thread my_thread(&worker, mainArg); /* Other code... */ my_thread.join();` No more casting, type-safe, object-oriented, simpler... ;) – Some programmer dude Dec 07 '20 at 07:51
  • I see. I think I'll stick with pthreads until i finish this book and maybe later I can switch to `std::threads`. I mainly want to learn theory about multi-threading. I think once I am fairly competent with the theory I can switch APIs. Thanks for your input. @Someprogrammerdude – HariP19 Dec 07 '20 at 07:54

2 Answers2

2

pthread_join() takes the address of a pointer variable. That variable will receive the pointer value that the thread returns. It is effectively doing the same as this:

void doIt(void (*f)(), void** ptr)
{
    *ptr = f();
}

void* func()
{
    return ...; 
}

int main()
{
    void *ptr;
    doIt(func, &ptr);
    // ptr holds whatever func() returns...
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
2

How are these two types compatible?

When void** is indirected through, the result is lvalue of type void*


Your program has undefined behaviour. Here is a correct way:

void* void_main_result;

pthread_join(p, &void_main_result);

int* int_main_result = static_cast<int*>(void_main_result);
std::cout << "\nResult = " << *int_main_result;

You can avoid dynamic allocation by allocating the result within the arguments. That way the return value isn't needed:

struct ArgRet {
    ArgT args;
    int ret;
} mainArg;

pthread_create(&p, NULL, worker, &mainArg);

std::cout << "\nResult = " << mainArg.ret;

// in worker
ArgRet* myArg = static_cast<ArgRet*>(arg);
myArg->ret = myArg->args.a + myArg->args.b;
return arg; // or nullptr; doesn't really matter

what's the main advantage of using std::thread instead of pthreads

std::thread works in all C++ (11 or later) implementations. pthreads only work on POSIX systems. The API of std::thread is also easier to use since it is written in C++ rather than C.


P.S. Converting a pointer to void* is implicit. I recommend not using C-style casts.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thank you for your answer. Can you tell me what you think causes an undefined behavior? The book that I am reading currently had the same implementation and the output of the code also seems to be fine. – HariP19 Dec 07 '20 at 08:03
  • 1
    @HariP19 It is UB because `pthread_join` will indirect through the `void**` and assign to the lvalue in which case this standard rule applies: `If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined: - the dynamic type of the object, ...` The dynamic type of the object is `int*` and the lvalue that is used to access it in within `pthread_join` is `void*`. Those are not the same type. The rule has a list of other types that are allowed, none of which apply in this case. Looks like the book has a bug. – eerorika Dec 07 '20 at 08:08