我有这个POSIX线程:
void subthread(void)
{
while(!quit_thread) {
// do something
...
// don't waste cpu cycles
if(!quit_thread) usleep(500);
}
// free resources
...
// tell main thread we're done
quit_thread = FALSE;
}
现在我想从主线程终止subthread()。我尝试了以下内容:
quit_thread = TRUE;
// wait until subthread() has cleaned its resources
while(quit_thread);
但它不起作用!虽然我的子线程在释放资源后明确将quit_thread设置为FALSE,但是while()子句永远不会退出!
如果我像这样修改我的关机代码:
quit_thread = TRUE;
// wait until subthread() has cleaned its resources
while(quit_thread) usleep(10);
然后一切正常!有人可以向我解释为什么第一个解决方案不起作用以及为什么带有usleep(10)的版本突然有效?我知道这不是一个很好的解决方案。我可以使用信号量/信号,但我想了解多线程,所以我想知道为什么我的第一个解决方案不起作用。
谢谢!
答案 0 :(得分:2)
如果没有memory fence,则无法保证在一个线程中写入的值将出现在另一个线程中。大多数pthread原语引入了一个障碍,就像usleep
这样的几个系统调用一样。在读和写周围使用互斥锁会引入障碍,并且更一般地防止多字节值在部分写入状态下可见。
您还需要将要求线程停止执行的想法分开,并报告它已停止,并且似乎对两者使用相同的变量。
答案 1 :(得分:1)
while(quite_thread);
使用的值quit_thread
设置为前一行。调用函数(usleep
)会导致编译器在每次测试时重新加载值。
在任何情况下,这都是等待线程完成的错误方法。请改用pthread_join
。
答案 2 :(得分:1)
你正在以错误的方式“学习”多线程。正确的方法是学习使用互斥锁和条件变量;任何其他解决方案在某些情况下都会失败
答案 3 :(得分:1)
最可能发生的是你的编译器不知道quit_thread
可以被另一个线程更改(因为C不知道线程,至少在提出这个问题的时候)。因此,它将while
循环优化为无限循环。
换句话说,它会查看此代码:
quit_thread = TRUE;
while(quit_thread);
并且自己想,“哈,那个循环中的任何东西都不能将quit_thread
更改为FALSE,因此编码器显然只是想写while (TRUE);
”。
当您将调用添加到usleep
时,编译器会考虑另一个并假设函数调用可能更改全局,因此它可以安全地播放并且不会优化它
通常你会将变量标记为volatile
以阻止编译器优化它,但在这种情况下,你应该使用pthreads提供的工具并在将标志设置为true后加入线程(并且没有子线程重置它,如果有必要,在连接后在主线程中执行此操作。原因是连接可能比等待变量更改的连续循环更有效,因为执行连接的线程很可能在连接需要完成之前不会被执行。
在您的旋转解决方案中,加入线程很可能会继续运行并吸收CPU咕噜声。
换句话说,做一些事情:
Main thread Child thread
------------------- -------------------
fStop = false
start Child Initialise
Do some other stuff while not fStop:
fStop = true Do what you have to do
Finish up and exit
join to Child
Do yet more stuff
而且,顺便说一下,你应该在技术上保护共享变量与互斥锁,但这是少数情况之一,它是可以的,单向通信,其中变量的半变化值无关紧要(假/不 - 假)。
通常互斥保护变量的原因是停止一个线程看到它处于半变化状态。假设你有一个两字节整数用于计算某些对象的数量,它被设置为0x00ff
(255)。
让我们进一步说线程A试图增加该计数,但它不是原子操作。它将顶部字节更改为0x01
,但在有机会将底部字节更改为0x00
之前,线程B突然进入并将其读取为0x01ff
。
如果线程B想要对该值计算的最后一个元素做某事,那么现在不会很好。它应该看0x0100
,但会尝试查看0x01ff
,如果不是灾难性的话,效果将是错误的。
如果count变量受互斥锁保护,则在线程A完成更新之前,线程B不会查看它,因此不会出现问题。
与单向布尔无关的原因是因为任何半状态也将被视为真或假,如果线程A在将0x0000
转换为0x0001
之间的中途(仅在顶部字节),线程B仍然会将其视为0x0000
(false)并继续(直到线程A下次完成更新)。
如果线程A将布尔值转换为0xffff
,则线程B仍将认为0xff00
的半状态为真,因此在线程A完成更新布尔值之前它将执行其操作。
这两种可能性都不是很糟糕,因为在两者中,线程A正在改变布尔值并最终完成。线程B是先检测到它还是稍微检测一点并不重要。