使用 void* 正确地将对象传递给 pthread_create()

Correctly passing an object to pthread_create() using void*

我一直在遵循 creating POSIX threads 上的说明,并密切关注他们的示例 Pthread 创建和终止

根据他们的示例,他们将 long 整数值传递给 pthread_create(),这导致调用类似于此:

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

我想要 运行 的代码草图是:

#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);
}

这是在作业的上下文中,老师希望我们在 Linux 中使用 gcc,因此我使用 Windows 子系统 Linux 以及这些编译参数:

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

运行 上面的程序会输出“线程的值:42”。

我的 C++ 指针经验比我晚了几年,但为了好玩,让我们将数据类型从 long 更改为 int。这会将语句更改为 int testVal = 42std::cout << "Value of thread: " << (int)testVal << '\n';。然而,这种情况发生了:

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

使用整数数据类型不是我想要做的,但我包括这个案例是因为我的猜测是问题从这里复合。

问题 如何将 Range 对象传递给 pthread_create()?我需要做一些事情,

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

...但是我遇到了一堆类型错误。研究有关传递此值的其他问题并没有带来太多清晰度。返回的主要问题是 从类型 'Range' 到类型 'void*' 的无效转换。我不太了解将 void 指针传递给某个值与将 void 指针传递给已寻址类型之间的区别,无法理解出了什么问题。测试指针引用、取消引用和强制转换的各种组合没有帮助。

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

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

这是因为您的 Range class 不能从指向 void 的指针转换。

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

通过使用间接寻址。 pthread_create 接受指向任何类型对象的 指针 。这就是 void* 的意思;它可以指向任何类型的对象。传递一个对象的地址:

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

可以将void*静态转换回原来的指针类型,然后通过指针间接访问指向的对象:

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

请注意,使用间接会给示例带来问题: 至少只要线程使用它,指向的对象就必须处于活动状态。这在您的示例中不起作用,因为该对象是 main 中的一个自动变量,该示例在启动另一个线程后立即终止。一种解决方案是通过加入等待另一个线程结束:

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

示例质量不高。它在将整数传递给线程时使用重新解释技巧来避免间接寻址,但不应将 long 重新解释为 void* 并返回,因为不能保证它们的大小相同 - 而且't in 64 bit Windows - 因此标准不保证其往返转换的有效性。该示例应该使用 std::intptr_t.


P.S。不要在 C++ 中使用 NULL

P.P.S。避免在 C++ 中使用 pthreads。更喜欢 std::thread

在开始手头的任务之前,您需要解决几个基本问​​题,即将 Range 的实例传递给新线程。

pthread_exit(NULL);

您将在 pthread_exit 的文档中找到有关 pthread_exit() 如何终止调用线程的说明。值得注意的是,在您的程序中,无论如何都不能保证 do_something 在调用 pthread_exit 之前实际执行。 pthread_create 给你的只是一个模糊的承诺,即创建一个新的执行线程,并将很快开始处理它的业务。无法保证它会在 pthread_create returns 之前 运行ning 开始,并且您无法保证新线程会在 pthread_exit 终止之前 运行。

那么,假设您以某种方式将指向您的 range 对象的指针传递给您的 do_something。好吧,当 do_something 尝试做某事时,实际的 range 对象已经消失了,你有一个指向已销毁对象的指针,使用它就变成了未定义的行为。

在你的 main 中,&range 给你一个指向这个对象的指针,通常,你可以转换为 void * 并将它传递给 pthread_create;它 do_something 你可以将它转换回 Range * 并使用它。但在您修复此未定义的行为之前,这是一个有实际意义的问题。

而不是修复它,您可以考虑使用 std::thread 而不是 POSIX 线程,它提供了一个更自然的、本机 C++ 执行线程实现,解决了大多数这些问题(当正确使用)。