为什么线程创建方法需要参数?

时间:2010-08-19 04:23:01

标签: c++ c multithreading

Windows中的所有线程创建方法(如pthread_create()或CreateThread())都希望调用者提供指向线程的arg的指针。这本身不安全吗?

只有当arg在堆中,然后再次创建堆变量时,这才能“安全”工作 增加了清理已分配内存的开销。如果提供堆栈变量作为arg,那么结果最多是不可预测的。

对我来说这看起来像是半熟的解决方案,还是我错过了API的一些微妙方面?

5 个答案:

答案 0 :(得分:5)

上下文。

许多C API提供额外的void *参数,以便您可以通过第三方API传递上下文。通常,您可以将一些信息打包到结构中并将此变量指向结构,这样当线程初始化并开始执行时,它比其开始的特定函数具有更多信息。没有必要在给定的位置保留此信息。例如,您可能有几个字段告诉新创建的线程将要处理的内容,以及它可以找到所需数据的位置。此外,并不要求void *实际上用作指针,它是在给定体系结构(指针宽度)上具有最合适宽度的无类型参数,任何东西都可以用于新线程。例如,如果intsizeof(int) <= sizeof(void *),您可以直接传递(void *)3

作为这种风格的相关示例:我正在开发的FUSE文件系统首先打开一个文件系统实例,比如struct MyFS。在多线程模式下运行FUSE时,线程会到达一系列FUSE定义的调用来处理openreadstat等。当然,这些调用可能没有实际细节的预先知识我的文件系统,所以这是在用于此目的的fuse_main函数void *参数中传递的。 struct MyFS *blah = myfs_init(); fuse_main(..., blah);。现在,当线程到达上面提到的FUSE调用时,收到的void *将转换回struct MyFS *,以便可以在预期的MyFS实例的上下文中处理调用。

答案 1 :(得分:2)

  

这本身不安全吗?

没有。这是一个指针。由于您(作为开发人员)已经创建了将由线程执行的函数和将传递给线程的参数,因此您可以完全控制。请记住,这是一个C API(不是C ++),因此它可以安全到达。

  

只有当arg在堆中时,这才能“安全”工作

没有。只要父线程中的生命周期与子线程中可以使用的生命周期一样长,它就是安全的。有很多方法可以确保它的寿命足够长。

  

然后再次创建堆变量会增加清理已分配内存的开销。

严重。这是一个争论?因为这基本上是如何为所有线程完成的,除非你传递更简单的东西,如整数(见下文)。

  

如果提供堆栈变量作为arg,那么结果最多是不可预测的。

它可以像你(开发者)那样可以预测。您创建了线程和参数。您有责任确保论证的生命周期是恰当的。没有人说这很容易。

  

对我来说,这看起来像是半熟的解决方案,还是我错过了API的一些微妙方面?

您错过了这是最基本的线程API。它的设计尽可能灵活,因此可以使用尽可能少的字符串开发更安全的系统。因此,我们现在使用boost :: threads,如果我认为这些基本线程设施可以构建,但提供更安全,更易于使用的基础设施(但需要额外付费)。

如果您希望RAW不受限制的速度和灵活性,请使用C API(有一些危险)。 如果你想要稍微安全一点,可以使用更高级别的API,例如boost:thread(但稍微贵一些)

没有动态分配的线程特定存储(示例)

#include <pthread.h>
#include <iostream>

struct ThreadData
{
    // Stuff for my thread.
};

ThreadData  threadData[5];

extern "C" void* threadStart(void* data);

void* threadStart(void* data)
{
    intptr_t        id      = reinterpret_cast<intptr_t>(data);
    ThreadData&     tData   = threadData[id];

    // Do Stuff
    return NULL;
}


int main()
{
    for(intptr_t loop = 0;loop < 5; ++loop)
    {
        pthread_t   threadInfo; // Not good just makes the example quick to write.

        pthread_create(&threadInfo, NULL, threadStart, reinterpret_cast<void*>(loop));
    }
    // You should wait here for threads to finish before exiting.
}

答案 2 :(得分:1)

堆上的分配不会增加很多开销。

除了堆和堆栈之外,全局变量空间是另一种选择。此外,可以使用与子线程一样长的堆栈帧。例如,考虑main的本地变量。

我赞成将参数放在与pthread_t对象本身相同的结构中。所以无论你把pthread记录放在哪里,都要提出它的论点。问题解决了:v)。

答案 3 :(得分:0)

这是使用函数指针的所有C程序中的常用习惯用法,而不仅仅是用于创建线程。

想一想。假设您的函数void f(void (*fn)())只是调用另一个函数。你真的可以。通常,函数指针必须对某些数据进行操作。将数据作为参数传递是一种干净的方法来实现这一目标,而不使用全局变量。由于函数f()不知道该数据的目的是什么,它使用了通用的void *参数,并依赖程序员有意义它的。

如果你更习惯于面向对象编程思考,你也可以把它想象成在一个类上调用一个方法。在这个类比中,函数指针是方法,额外的void *参数相当于C ++会调用this指针:它为你提供了一些实例变量来操作上。

答案 4 :(得分:0)

指针是指向要在函数中使用的数据的指针。 Windows样式API要求您为它们提供静态或全局函数。

通常这是一个指向你打算使用指针的类的指针,如果你愿意,可以使用pThis,并且意图是在线程结束后删除pThis。

它是一种非常程序化的方法,但是它有一个非常大的优点,经常被忽略,CreateThread C样式API是二进制兼容的,所以当你用C ++类(或几乎任何其他语言)包装这个API时,你可以做这实际上是这样做的。如果输入了参数,您将无法轻松地从其他语言访问该参数。

所以是的,这是不安全的,但这是有充分理由的。