0
votes

I've been following the instructions on creating POSIX threads, and closely following their example Pthread Creation and Termination.

According to their example, they pass a long integer value to pthread_create(), which results in a call similar to this one:

pthread_create(&thread, NULL, do_something, (long*)testVal);

A sketch of the code I want to run is:

#include <iostream>
#include <pthread.h>
using namespace std;

// Simple example class.
class Range {
    public:
        int start;
        int end;
        Range(int start_bound, int end_bound) {
            start = start_bound;
            end = end_bound;
        }
        void print() {
            cout << "\n(" << start << ", " << end << ')';
        }
};

void* do_something(void* testVal) {
    std::cout << "Value of thread: " << (long)testVal << '\n';
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    Range range = Range(1, 2); // What I really want to pass
    long testVal = 42;         // Let's test

    pthread_create(&thread, NULL, do_something, (void*)testVal);
    pthread_exit(NULL);
}

This is in the context of an assignment where the teacher wants us to use gcc in Linux, so I'm using Windows Subsystem for Linux with these compilation parameters:

gcc program.cpp -o program -lstdc++ -lpthread

Running the above program will output "Value of thread: 42".

My C++ pointer experience is a few years behind me, but just for fun, let's change the datatype from long to int. This changes the statements to int testVal = 42 and std::cout << "Value of thread: " << (int)testVal << '\n';. However, this happens:

error: cast from ‘void*’ to ‘int’ loses precision (int)testVal

warning: cast to pointer from integer of different size pthread_create(&thread, NULL, do_something, (void*)testVal);

Using an integer datatype is not what I want to do, but I include this case because my guess is that the problem compounds from here.

Question How do I pass the Range object to pthread_create()? I need to do something like,

void* do_something(void* range) {
    (Range)range.print();
...

...but am confronted with a bunch of type errors. Researching other questions about passing this value has not brought much clarity. The main problem that comes back is invalid cast from type 'Range' to type 'void*'. I'm not versed enough in the difference between passing a void pointer to a value to versus passing a void pointer to an addressed type to understand what's going wrong. Testing various combinations of pointer reference, dereference, and casting has not helped.

2
(void*)&testVal address of testVal - Jeffrey

2 Answers

1
votes

There are a couple of fundamental issues that you need to address before getting to the task at hand, of passing an instance of your Range to your new thread.

pthread_exit(NULL);

You will find an explanation in pthread_exit's documentation how pthread_exit() terminates the calling thread. Notably, in your program, there is no guarantee, whatsoever, that do_something actually executes before pthread_exit gets called. All that pthread_create gives you is a vague promise that a new execution thread gets created, and will start going about it's business sometime soon. There is no guarantee that it'll start running before pthread_create returns, and you have no guarantees that the new thread will run before pthread_exit terminates.

So, let's suppose somehow you're passing a pointer to your range object to your do_something, somehow. Well, by the time do_something tries to do something, the actual range object is already gone, and you have a pointer to a destroyed object, and using it becomes undefined behavior.

In your main, &range gives you a pointer to this object, normally, and you can convert to a void * and pass it to pthread_create; and it do_something you can convert it back to a Range * and use it. But this is a moot point until you fix this undefined behavior.

And instead of fixing it you might consider using std::thread instead, rather than POSIX threads, which offers a more natural, native C++ implementation of execution thread that solves most of these problems (when used correctly).

1
votes
void* do_something(void* range) {
    (Range)range.print();
...

...but am confronted with a bunch of type errors.

This is because your Range class isn't convertible from a pointer to void.

Question How do I pass the Range object to pthread_create()?

By using indirection. pthread_create accepts pointer to an object of any type. That's what void* means; it can point to an object of any type. Pass the address of an object:

Range range = Range(1, 2);
Range* range_ptr = &range;

pthread_create(&thread, nullptr, do_something, range_ptr);
// or shorter
pthread_create(&thread, nullptr, do_something, &range);

You can static cast the void* back to the original pointer type, and then you can indirect through the pointer to access the pointed object:

void* do_something(void* void_ptr) {
    Range* range_ptr = static_cast<Range*>(void_ptr);
    range_ptr->print();

Note that using indirection introduces a problem to the example: The pointed object has to be alive at least as long as the thread uses it. This doesn't work in your example case since the object is an automatic variable in main, which the example terminates immediately after starting the other thread. One solution is to wait for the other thread to end by joining:

// pthread_exit(NULL); <-- replace this 
pthread_join(thread, nullptr);

The example is not of high quality. It uses a reinterpretation trick to avoid indirection when passing an integer to the thread, but it should not be reinterpreting long as void* and back, because they aren't guaranteed to be of same size - and aren't in 64 bit Windows - and therefore standard doesn't guarantee validity of their round trip conversion. The example should have been using std::intptr_t.


P.S. Don't use NULL in C++.

P.P.S. Avoid using pthreads in C++. Prefer std::thread instead.