简单的POSIX线程问题

时间:2010-05-14 13:50:27

标签: linux multithreading posix pthreads

我有这个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)的版本突然有效?我知道这不是一个很好的解决方案。我可以使用信号量/信号,但我想了解多线程,所以我想知道为什么我的第一个解决方案不起作用。

谢谢!

4 个答案:

答案 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是先检测到它还是稍微检测一点并不重要。