提升线程 - 赛车

时间:2011-01-31 06:16:09

标签: c++ multithreading boost

我正在玩一些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>

2 个答案:

答案 0 :(得分:4)

这里有几件事情阻止你看到你期望的上下文切换行为:

  1. 线程基本上坐在紧密的循环中。上下文切换不太可能发生在中间。
  2. w(2)打印6,然后47时,您的示例中确实有一个奇怪的上下文切换。它从3切换到2然后再返回。
  3. C ++编译器可能正在优化对全局变量的访问。变量volatile可能有所帮助。请注意,volatile并不是真正的解决方案。例如,增量可能不是原子操作(也就是获取,增量,存储),并且CPU缓存有时也会导致奇怪的伪像,因为所有CPU在执行时都看不到存储到内存。
  4. 您没有使用endl。通常情况下,我认为endl是一个绝对可怕的想法。但在这种情况下,这意味着所有写入都被缓冲,这可能会导致一些有趣的工件。
  5. 此外,cout将是线程安全的,并且实现此目的所需的锁定也可能影响线程上下文相互切换的方式。
  6. 通过直接调用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或向数组添加一个元素