[英]Why it doesn't work? Simple multithreading example
你能幫我理解為什么這段代碼會凍結程序嗎?
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int i = 0;
mutex mx;
void foo() {
while(1) {
lock_guard<mutex> locker(mx);
i++;
if(i == 5000) {
break;
}
}
}
void boo() {
while(1) {
if(i == 100) {
lock_guard<mutex> locker(mx);
i = 5000;
break;
}
}
}
int main(int argc, char *argv[])
{
thread th1(foo);
thread th2(boo);
th1.join();
th2.join();
return 0;
}
為什么我會有這樣的結果? 如何更改代碼以使其正確? 你能告訴我你的想法嗎? 謝謝。
即使boo
先開始運行,它也可能永遠不會看到i==100
。
如果您只有一個 CPU,那么當i==100
時,CPU 不太可能從foo
切換到boo
。
如果你有多個 CPU,那么i==100
可能永遠不會進入foo's
緩存,因為i
不是易失性的,並且互斥鎖在讀取之間沒有鎖定。
實際上,編譯器在第一次之后甚至不必讀取i
,因為沒有 memory 障礙。 它可以假設值沒有改變。
即使你要解決這個問題,在boo
注意到之前i
可能會增加超過 100 的明顯可能性仍然存在。 看起來您希望這兩個線程“輪流”,但事實並非如此。
您的解決方案存在一些並發問題:
您必須始終如一地鎖定互斥體。 所有對i
的訪問都必須受到互斥量的保護,因此在if (i == 100) {
行也是如此。 在沒有同步的情況下,編譯器可以自由地優化線程,就好像它在孤立地運行一樣,並假設i
永遠不會改變。
不能保證boo
會在foo
之前開始。 如果它在之后開始, i
將已經增加到遠高於100
。
不能保證互斥鎖定是公平的。 競爭同一個互斥量的兩個線程不會以交錯方式運行。 這意味着foo
可能會在boo
有機會運行之前多次增加i
,因此boo
看到的i
的值可能很容易從0
跳到1000
,跳過所需的100
。
孤立地, foo
將“逃跑”,將i
增加到遠遠超過5000
。 應該有一些退出或重新啟動條件。
如何更改代碼以使其正確?
添加一些同步以強制交錯處理。 例如,使用condition_variable
s 在線程之間發出信號:
int i = 0;
mutex mx;
condition_variable updated_cond;
bool updated = false;
condition_variable consumed_cond;
bool consumed = true;
void foo() {
while (1) {
unique_lock<mutex> locker(mx);
consumed_cond.wait(locker, [] { return consumed; });
consumed = false;
if (i == 5000) {
break;
}
std::cout << "foo: i = " << i << "+1\n";
i++;
updated = true;
updated_cond.notify_one();
}
std::cout << "foo exiting\n";
}
void boo() {
for (bool exit = false; !exit; ) {
unique_lock<mutex> locker(mx);
updated_cond.wait(locker, [] { return updated; });
updated = false;
std::cout << "boo: i = " << i << "\n";
if (i == 100) {
i = 5000;
exit = true;
}
consumed = true;
consumed_cond.notify_one();
}
std::cout << "boo exiting\n";
}
該程序的行為是未定義的,因此對其所做的推理是徒勞的。 問題是boo
讀取i
的值, foo
都讀取和寫入i
的值,但是boo
中if (i == 100)
中i
的讀取相對於發生在foo
中的寫入是無序的。 那是一場數據競賽,程序的行為是未定義的。 當然,您可以猜測可能會發生什么,但如果您希望代碼正確運行,則必須確保不存在數據競爭。 這意味着使用某種形式的同步:要么將boo
中的鎖移到if
之前,要么擺脫互斥並將i
的類型更改為std::atomic<int>
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.