我正在玩一些Boost线程,我编写了以下代码:
#include <boost/thread.hpp>
#include <iostream>
volatile int threads = 0;
const int iterations = 200;
volatile char output[iterations*4];
volatile int k = 0;
void w() {
threads++;
for (int i = 0; i < iterations; i++) {
k++;
output[k] = '0';
}
}
void y() {
threads++;
for (int i = 0; i < iterations; i++) {
k++;
output[k] = '1';
}
}
int main(int argc, char* argv[]) {
boost::thread a(&w);
boost::thread b(&w);
boost::thread c(&y);
boost::thread d(&y);
while (k <= iterations * 4) {}
for (int i=0; i < k; i++) {
std::cout << output[i];
}
return 0;
}
我不明白为什么这些线程的完成似乎在彼此等待,这在输出中显示:
000000000000000000000000000000000000000011111111
我的印象是输出将是随机顺序,语句输出类似于以下(预期输出):
00100000111001000000010001111100000000011111111
相反,他们跟随,全部为0,然后全部为1(反之亦然)。
这是为什么?在这一点上,似乎更像线程以随机顺序堆叠,但仍然加入;即在执行前互相等待。我实际上期望线程++代码可能在某些线程中同时运行,留下一些thisthread值。
免责声明:我对Boost非常陌生:线程,只是玩弄,我知道竞争条件非常危险(可能导致未定义的行为),这只是把我的手放在火中,所以我能理解它。 / p>
答案 0 :(得分:4)
这里有几件事情阻止你看到你期望的上下文切换行为:
w(2)
打印6
,然后47
时,您的示例中确实有一个奇怪的上下文切换。它从3切换到2然后再返回。volatile
可能有所帮助。请注意,volatile
并不是真正的解决方案。例如,增量可能不是原子操作(也就是获取,增量,存储),并且CPU缓存有时也会导致奇怪的伪像,因为所有CPU在执行时都看不到存储到内存。endl
。通常情况下,我认为endl
是一个绝对可怕的想法。但在这种情况下,这意味着所有写入都被缓冲,这可能会导致一些有趣的工件。cout
将是线程安全的,并且实现此目的所需的锁定也可能影响线程上下文相互切换的方式。通过直接调用write
系统调用可以解决许多这些问题。这将强制进行系统调用,这可能使另一个线程有机会运行。它还会强制输出恰好在执行代码时发生。
这是一个程序,它将展示您在多核Unix机器上寻找的行为。它还会显示k
的最终值,以说明为什么volatile
不是一个真正的解决方案,以及它如何在值k
上创建竞赛。我最初只是建议它作为一个黑客,让你的程序稍微更好地产生你正在寻找的行为,因为在你的原始程序中它并不重要:
#include <boost/thread.hpp>
#include <iostream>
#include <unistd.h>
volatile int threads = 0;
const int iterations = 200;
volatile int k = 0;
void w(char thid) {
threads++;
for (int i = 0; i < iterations; i++) {
k++;
::write(1, &thid, 1);
}
}
int main(int argc, char* argv[]) {
boost::thread a(&w, '0');
boost::thread b(&w, '1');
boost::thread c(&w, '2');
boost::thread d(&w, '3');
a.join();
b.join();
c.join();
d.join();
::write(1, "\n", 1);
// In general, do not mix write to stdout and cout. In this case
// it will work, but this is a very limited and specific case.
::std::cout << "k == " << k << "\n";
return 0;
}
答案 1 :(得分:2)
观察输出缓冲区的顺序填充的主要原因是线程太快完成。 200次迭代不足以使工作线程被中断。我没有打印输出,而是将其附加到main()
:
int switches = 0;
for (int i=1; i<iterations*4; ++i)
if (output[i] != output[i-1])
++switches;
std::cout << "Switches = " << switches << "\n";
并开始增加iterations
。实际上,在某些时候(我的盒子上大约10,000,000)线程切换变得明显。这立即揭示了程序的另一个问题:k
的增量不是原子的。您已将k
声明为volatile,这意味着编译器在重复检查时不会将其值缓存在寄存器中,就像在main
中的空循环中一样。但是,挥发性物质根本不能保证原子增量。这在实践中意味着什么是实际线程切换发生时,k++
并不总是递增,while (k<iterations*4) {}
循环永远不会终止。要原子地增加k
,您需要另一个外部库 - here's a discussion on the topic on SO。如果您碰巧在Windows上,可能更容易直接使用InterlockedIncrement。如果您对程序进行了这些更改,您将看到您期望的结果。
还有一个微妙的缓冲区溢出输出。由于您在写入输出之前递增k,因此您将写入从1到包含的迭代* 4的索引。您需要将k初始化为-1或向数组添加一个元素