0

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.

1
  • (void*)&testVal address of testVal Commented Feb 9, 2021 at 23:52

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

4 Comments

A good pattern to start with is to allocate the object with new, pass a pointer to the thread, cast the pointer to the correct type in the thread, and have the thread delete the object when it's done with it. This not only will work correctly with any object type, but it also makes it a bit harder to mess up the type safety and lifetime.
Thanks, I would never have figured that line out. Also, pthread figures in this question because that's the assignment's purpose; of course it would be quickest to use std::thread.
The documentation I'm referencing explicitly warns against passing the objet's address because it "is shared memory space and visible to all threads. As the loop iterates, the value of this memory location changes, possibly before the created threads can access it." In fact, I believe I'm experiencing this problem.
@DenisG.Labrecque You don't modify the object from any thread after its creation in your example, so that problem doesn't seem to be demonstrated. You do need to sychronise if you access shared memory from more than one thread and at least one thread writes to it. "Pass object to thread" and "Don't use shared memory" are pretty much contradictory except for the special case of reinterpreting the pointer as an integer.
1

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

3 Comments

Thank you, the goal of this exercise is to understand what threading requires under the hood, so std::thread, in this case, may not be used. Your comments, however, are useful for understanding what happens, and give me a direction for fixing my hacked-together demo.
Are you referring to pthread_exit() in the function or at the end of main (or both)? Removing it from the function causes the function not to have a return. Also, shouldn't the use of pthread_join() preclude worries of using this in main?
I was referring to pthread_exit() in main(). And, yes, pthread_join() will wait for the new thread to finish.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.