[英]Boost Threads - Racing
我正在玩一些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:threads非常陌生,只是玩轉而已,我知道比賽條件是非常危險的(可能導致不確定的行為),這只是把我的手放在火上,所以我可以理解它。
這里發生了幾件事,使您無法看到期望的上下文切換行為:
w(2)
打印6
,然后打印47
時,您的示例中確實有一個奇怪的上下文切換。 它從3切換到2,然后再次返回。 volatile
可能會有所幫助。 注意, volatile
並不是真正的解決方案。 例如,增量可能不是原子操作(又名獲取,增量,存儲),而且CPU緩存有時可能會產生怪異的偽影,因為在執行所有內存時,並不是所有CPU都能看到存儲在內存中。 endl
。 通常,我認為endl
是一個絕對可怕的想法。 但是在這種情況下,這意味着所有寫操作都被緩沖了,這可能會引起一些有趣的問題。 cout
將是線程安全的,並且實現此操作所需的鎖定也可能會影響線程上下文彼此切換的方式。 這些問題中的許多問題可以通過直接調用write
系統調用來解決。 這將強制進行系統調用,這可能會給另一個線程運行的機會。 這也將強制輸出在執行其代碼時准確發生。
這是一個程序,它將展示您在多核Unix機器上想要的行為。 它還將向您展示k
的最終值,以說明為什么volatile
並不是真正的解決方案,以及它如何在值k
上產生競爭。 我最初只是將其建議為一種hack,目的是使您的程序在產生所需的行為方面更好地工作,因為在您的原始程序中這並不重要:
#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;
}
觀察輸出緩沖區順序填充的主要原因是線程完成得太早。 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
中的空循環中那樣將其值緩存在寄存器中。 但是,volatile根本不能保證原子增量。 實際上,這意味着當實際的線程切換發生時, k++
並不總是遞增, while (k<iterations*4) {}
循環永遠不會終止。 要原子地遞增k
您還需要另一個外部庫- 這是關於SO主題的討論 。 如果您碰巧在Windows上,直接使用InterlockedIncrement可能會更容易。 如果對程序進行了這些更改,您將看到預期的結果。
還有一個微妙的緩沖區溢出輸出。 由於要在寫入輸出之前遞增k,因此要寫入的索引從1到包含迭代次數* 4。 您要么需要將k初始化為-1,要么需要向數組中添加一個元素
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.