Windows中的所有线程创建方法(如pthread_create()或CreateThread())都希望调用者提供指向线程的arg的指针。这本身不安全吗?
只有当arg在堆中,然后再次创建堆变量时,这才能“安全”工作 增加了清理已分配内存的开销。如果提供堆栈变量作为arg,那么结果最多是不可预测的。
对我来说这看起来像是半熟的解决方案,还是我错过了API的一些微妙方面?
答案 0 :(得分:5)
上下文。
许多C API提供额外的void *
参数,以便您可以通过第三方API传递上下文。通常,您可以将一些信息打包到结构中并将此变量指向结构,这样当线程初始化并开始执行时,它比其开始的特定函数具有更多信息。没有必要在给定的位置保留此信息。例如,您可能有几个字段告诉新创建的线程将要处理的内容,以及它可以找到所需数据的位置。此外,并不要求void *
实际上用作指针,它是在给定体系结构(指针宽度)上具有最合适宽度的无类型参数,任何东西都可以用于新线程。例如,如果int
:sizeof(int) <= sizeof(void *)
,您可以直接传递(void *)3
。
作为这种风格的相关示例:我正在开发的FUSE文件系统首先打开一个文件系统实例,比如struct MyFS
。在多线程模式下运行FUSE时,线程会到达一系列FUSE定义的调用来处理open
,read
,stat
等。当然,这些调用可能没有实际细节的预先知识我的文件系统,所以这是在用于此目的的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时,你可以做这实际上是这样做的。如果输入了参数,您将无法轻松地从其他语言访问该参数。
所以是的,这是不安全的,但这是有充分理由的。