我不太明白为什么这个简单的网络代码会阻止竞争条件
int main(void) {
...
listen(sd, SOMAXCONN);
for(;;) {
pthread_t t; int *socket;
socket = malloc(sizeof(int));
*socket = accept(sd, NULL, NULL);
pthread_create(&t, NULL, service_request, socket);
pthread_detached(&t);
}
...
}
我得到的解释是,套接字描述符值必须在堆中分配,以避免另一个接受覆盖其值的竞争条件。我不明白的是为什么。如果我只是写:
int main(void) {
...
listen(sd, SOMAXCONN);
for(;;) {
pthread_t t; int socket;
socket = accept(sd, NULL, NULL);
pthread_create(&t, NULL, service_request, socket);
pthread_detached(&t);
}
...
}
我按值传递套接字描述符"套接字"。无论如何,下一次接受将在 for 循环的另一个迭代中调用pthread_create()
并且复制"socket"的值之后发生。
现在我不确定在 main()
中调用过程 pthread_create()
时还是在另一个时刻复制该值。在第二种情况下,我会理解竞争条件。
pthread_create()
将指针参数传递给启动例程。您只需传递一个整数。因此,您假设 filedescriptor 整数可以(隐式)强制转换为指针值并返回而不会损坏。这可能是正确的,因为文件描述符通常低于 16 位,指针值的大小通常为>= 16 位,但它仍然丑陋且容易出错。
因此,malloc()
版本是首选,如第一个示例所示。
他们对"竞争条件"的意思是这个版本:
for(;;) {
pthread_t t; int socket;
socket = accept(sd, NULL, NULL);
pthread_create(&t, NULL, service_request, &socket);
pthread_detached(&t);
}
在这里,启动例程必须在下一次接受发生之前评估文件描述符值。否则,旧值将被覆盖,不能再使用。
你是对的,你不需要动态分配(例如使用 malloc
) 要传递的值。
然而
,线程函数(和pthread_create
)接受指针而不是值,这是大多数问题的原因,因为许多人只是使用地址运算符&
将指针传递给变量:
pthread_create(&t, NULL, service_request, &socket);
这确实会导致争用条件,因为在线程函数取消引用指针并使用该值之前,socket
的值可能会发生变化。
但是对于简单的整数(如示例中的socket
变量),有一个简单的解决方法,不包括使用malloc
:将值强制转换为指针。这实际上是为数不多的可以进行此类铸造的场合之一:
pthread_create(&t, NULL, service_request, (void *) (intptr_t) socket);
在线程函数中,您执行相反的转换:
void *service_request(void *pointer)
{
int socket = (int) (intptr_t) pointer;
...
}