您能澄清一下,为什么以下代码是将参数传递到新线程的安全方法:
//Listing 5.3 Passing a Value into a Created Thread
for ( int i=0; i<10; i++ )
pthread_create( &thread, 0, &thread_code, (void *)i );
以下代码不是:
//Listing 5.4 Erroneous Way of Passing Data to a New Thread
for ( int i=0; i<10; i++ )
pthread_create( &thread, 0, &thread_code, (void *)&i );
从书中引用,关于代码:
至关重要的是要意识到子线程可以在调用后的任何时刻开始执行,因此指针必须指向仍然存在的内容并仍然保留相同的值。这排除了传递指向更改变量的指针以及指向堆栈中保存的信息的指针(除非在子线程读取值之后堆栈肯定存在)。
答案 0 :(得分:3)
第三种方法很好,如下所示:
static int args[10];
for ( int i=0; i<10; i++ ) {
args[i] = i;
pthread_create( &thread, 0, &thread_code, (void *)&args[i] );
}
如果您希望在所有线程中共享相同的变量,请在main或最好的静态或全局变量中创建一个局部变量。
方法1和方法2的问题:
方法1 您正在向int
投射void *
,然后返回int
,int
大小void *
和void *
{1}}可能有所不同。如果您打算将int *
投射到i
,那就更糟了,还有一个UB。另请阅读this post。
方法2 您将相同的地址传递给所有线程。当i
从10个工作线程中的任何一个的主线程更改时,相同的值将反映在任何地方,这可能不是您的意图。 此外{{1}}的范围在for循环之后结束,并且您最终可能会访问线程中的悬空指针。 并且会导致UB。 (未定义的行为)
答案 1 :(得分:2)
正如您的引文所说,您不能将指针传递给interation变量,因为它会快速更改。你永远不知道并发线程何时会使用指针并取消引用它。
// Listing 5.4 Erroneous Way of Passing Data to a New Thread
for ( int i=0; i<10; i++ )
pthread_create( &thread, 0, &thread_code, (void *)&i );
想象一下第一次打电话给pthread_create()
。它接收指向i
的指针,可能会取消引用指针并读取值。您的值当时应为0
。但是你的主线程(带有for循环的主线程)可能已经从i
更改为0
到1
。这被称为竞争条件,因为您的程序取决于一个线程是否更快地更改值,或另一个线程更快获得它。
还有第二种竞争条件,因为你的i
变量将在循环结束时超出范围。如果线程启动缓慢或读取指针目标,则堆栈上的地址已经可以分配给其他东西。您不能取消引用不再存在的变量的指针。
第一个示例使用i
的值,而不是它的地址。这很好,因为pthread_create()
将保留该值并将其传递给线程。
// Listing 5.3 Passing a Value into a Created Thread
for ( int i=0; i<10; i++ )
pthread_create( &thread, 0, &thread_code, (void *)i );
但是pthread_create()
只接受void *
(通用指针)。该示例使用特殊技巧,您可以将整数值转换为指针值。预计线程函数将执行相反的操作(将指针转换回整数)。
此技巧通常用于存储期望对象的整数值,因为它避免了必须分配和释放对象。这种技术是好还是坏都不属于事实答案的范围。它被用在像GLib这样的框架中,但我想很多程序员都会嘲笑它。
书中的例子显然不是真正问题的解决方案,而只是动机例子。在实际代码中,您很少会传递一个整数值,并且您可能希望在某个时间点加入该线程。因此,在一个简单的场景中,您必须分配线程参数,填充它们,启动worker,加入worker,检索结果并释放分配。
在一个更复杂的场景中,您将与线程进行通信,因此您不会受限于在创建时提供它们并在加入它们后检索结果。您甚至可以让工作人员在需要时运行并重复使用它们。