【问题标题】:Correctly passing an object to pthread_create() using void*使用 void* 正确地将对象传递给 pthread_create()
【发布时间】:2021-05-13 16:07:23
【问题描述】:

我一直遵循creating POSIX threads 上的说明,并密切关注他们的示例Pthread Creation and Termination

根据他们的示例,他们将一个 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 的作业的上下文中,所以我正在使用带有这些编译参数的 Linux 的 Windows 子系统:

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

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

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

错误:从“void*”转换为“int”会丢失精度 (int)testVal

警告:从不同大小的整数转换为指针 pthread_create(&thread, NULL, do_something, (void*)testVal);

使用整数数据类型不是我想要做的,但我将这种情况包括在内,因为我的猜测是问题从这里开始复合。

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

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

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

【问题讨论】:

  • (void*)&amp;testVal testVal的地址

标签: c++ pthreads


【解决方案1】:
void* do_something(void* range) {
    (Range)range.print();
...

...但是遇到了一堆类型错误。

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

问题如何将 Range 对象传递给 pthread_create()?

通过使用间接。 pthread_create 接受 pointer 指向任何类型的对象。这就是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* 并返回,因为不能保证它们的大小相同 - 并且不在 64位 Windows - 因此标准不保证其往返转换的有效性。该示例应该一直使用std::intptr_t


附:不要在 C++ 中使用NULL

附言避免在 C++ 中使用 pthread。更喜欢std::thread

【讨论】:

  • 一个好的开始模式是使用new分配对象,将指针传递给线程,将指针转换为线程中的正确类型,并让线程delete对象完成后。这不仅适用于任何对象类型,而且还使得混淆类型安全和生命周期变得更加困难。
  • 谢谢,我永远也想不出那条线。另外,pthread 出现在这个问题中,因为这是作业的目的;当然使用std::thread 会最快。
  • 我引用的文档明确警告不要传递对象的地址,因为它“是共享内存空间并且对所有线程可见。随着循环迭代,此内存位置的值可能会在创建的线程可以访问它。”事实上,我相信我遇到了这个问题。
  • @DenisG.Labrecque 在示例中创建对象后,您不会从任何线程修改对象,因此似乎没有证明该问题。如果您从多个线程访问共享内存并且至少有一个线程写入共享内存,则您确实需要同步。除了将指针重新解释为整数的特殊情况外,“将对象传递给线程”和“不要使用共享内存”几乎是矛盾的。
【解决方案2】:

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

pthread_exit(NULL);

您将在pthread_exit 的文档中找到pthread_exit() 如何终止调用线程的解释。值得注意的是,在您的程序中,无论如何都不能保证do_somethingpthread_exit 被调用之前实际执行。 pthread_create 给你的只是一个模糊的承诺,即创建一个新的执行线程,并且很快就会开始执行它的业务。无法保证它会在pthread_create 返回之前开始运行,您也无法保证新线程会在pthread_exit 终止之前运行。

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

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

您可以考虑使用std::thread 而不是修复它,而不是使用 POSIX 线程,后者提供了更自然的、本机 C++ 执行线程实现,可以解决大部分问题(如果使用正确)。

【讨论】:

  • 谢谢,本练习的目的是了解线程在底层需要什么,因此在这种情况下,可能不会使用std::thread。但是,您的 cmets 对于了解发生的情况很有用,并为我提供了修复我的 hacked-together 演示的方向。
  • 你指的是pthread_exit()在函数中还是在main(或两者)的末尾?从函数中删除它会导致函数没有返回。此外,使用 pthread_join() 是否应该排除在 main 中使用它的担忧?
  • 我在main() 中指的是pthread_exit()。而且,是的,pthread_join() 将等待新线程完成。
猜你喜欢
  • 1970-01-01
  • 2017-05-05
  • 2013-11-26
  • 1970-01-01
  • 1970-01-01
  • 2019-08-24
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
相关资源
最近更新 更多