2016-02-25 1447 views
0

我有以下問題。我有一些多線程可以完成一些工作,一個主線程可以在工作時喚醒它們。到目前爲止,我已經設法使用條件變量和互斥鎖編寫一些代碼,並且大部分時間都可以正常工作,但通知線程不時會在調用notify_one()後立即鎖定互斥鎖,從而阻止通知的線程和死鎖。notify_one()(通知線程鎖互斥)後的死鎖

我寫了最小的代碼來說明這種情況。

#include <iostream> 
#include <thread> 
#include <condition_variable> 

std::mutex lock; 
std::condition_variable cv; 

void foo() { 
    std::cout << "Thread: Entering doWork()" << std::endl; 
    std::unique_lock<std::mutex> l(lock); 
    std::cout << "Thread: Acquired lock, going to wait." << std::endl; 
    cv.wait(l , []{return true;}); 
    std::cout << "Thread: Done waiting, exit." << std::endl; 
} 

int main(void) { 
    std::unique_lock<std::mutex> l(lock); 
    std::cout << "MAIN: Creating thread." << std::endl; 
    std::thread t(foo); 
    std::cout << "MAIN: Unlocking mutex." << std::endl; 
    l.unlock(); 
    std::cout << "MAIN: Notifying thread." << std::endl; 
    cv.notify_one(); 
    //std::this_thread::sleep_for(std::chrono::seconds(1)); 
    l.lock(); 
    std::cout << "MAIN: Acquired lock." << std::endl; 
    std::cout << "MAIN: Joining thread." << std::endl; 
    t.join(); 
    return 0; 
} 

在理想的情況下,輸出應該是

MAIN: Creating thread. 
MAIN: Unlocking mutex. 
Thread: Entering doWork() 
Thread: Acquired lock, going to wait. 
MAIN: Notifying thread. 
Thread: Done waiting, exit. 
MAIN: Acquired lock. 
MAIN: Joining thread. 

,但往往不是它是

MAIN: Creating thread. 
MAIN: Unlocking mutex. 
MAIN: Notifying thread. 
MAIN: Acquired lock. 
MAIN: Joining thread. 
Thread: Entering doWork() 

是否有消除僵局的機會,只是加入的睡眠沒有更好的辦法進入通知線程(我不想這樣做)?先謝謝你。

+1

您的'cv.wait()'不處理虛假的喚醒。根據http://en.cppreference.com/w/cpp/thread/condition_variable/wait:原子釋放鎖,阻塞當前正在執行的線程,並將其添加到等待* this的線程列表中。當執行notify_all()或notify_one()時,線程將被解除阻塞。它也可能被虛假地阻止。無阻塞時,無論原因如何,都會重新獲得鎖定並等待退出。 –

+1

你已經錯過了'notify' /'wait'方案的整個邏輯!你忘了執行你正在等待的東西,它的變化線程需要被通知!你有一個'wait',它不會等待*任何事情和一個不通知任何事情的'notify'! –

+1

@AndrewHenle,線程確實釋放了鎖,這就是'unique_lock'的用途。如果它測試了一個適當的條件,而不是'[] {return true;}',它會處理虛假的喚醒,因爲謂詞在喚醒時會被測試,如果謂詞是false,它會再次等待。 –

回答

1

它是「條件變量」而不是「條件變量」,它被稱爲的原因是您使用它在某些條件下等待。

你不這樣做,你只是等待一個總是返回true的lambda,這就是你的問題的原因。這和你的主線始終保持鎖定的原因(爲什麼?!)

有時main線程在foo線程啓動之前快速運行unlock,notify_one和鎖定。這意味着foo線程未命中通知,然後嘗試獲取鎖,但不能因爲主線程擁有它。

條件變量不像信號量,notify_one`調用不會設置稍後可以檢測到的狀態。如果在notify_one調用發生時條件變量沒有等待,那麼它會錯過它,並且它永遠消失了。如果你錯過了通知,然後睡覺,你永遠不會醒來。

的解決方案是添加任意的睡覺,不解決任何問題(永遠!)

正確的解決辦法是讓被測試的情況,當你」停止持有鎖不要更新任何共享數據。在下面的例子中,被測試的條件是「布爾型ready是真的嗎?」並且foo線程將等待,直到該條件爲真。主線程設置變量,使條件成立,然後通知其他線程它應該重新檢查條件。

#include <iostream> 
#include <thread> 
#include <condition_variable> 

std::mutex lock; 
std::condition_variable cv; 
bool ready = false; 

void foo() { 
    std::cout << "Thread: Entering doWork()" << std::endl; 
    std::unique_lock<std::mutex> l(lock); 
    std::cout << "Thread: Acquired lock, going to wait." << std::endl; 
    cv.wait(l , []{return ready;}); 
    std::cout << "Thread: Done waiting, exit." << std::endl; 
} 

int main(void) { 
    std::cout << "MAIN: Creating thread." << std::endl; 
    std::thread t(foo); 
    { 
     std::cout << "MAIN: Locking mutex." << std::endl; 
     std::unique_lock<std::mutex> l(lock); 
     ready = true; 
    } 
    std::cout << "MAIN: Notifying thread." << std::endl; 
    cv.notify_one(); 
    std::cout << "MAIN: Joining thread." << std::endl; 
    t.join(); 
} 
+0

是的,我錯過了我在實際代碼中有謂詞。還有一個問題,爲什麼這個互斥體鎖定在主體中呢? – Lubo

+1

在你的例子中,鎖定主要的互斥鎖是沒有意義的,它是完全沒有意義的並導致死鎖。在我的例子中,互斥鎖保護了正在被測試的「就緒」變量,即「條件」。因爲這個變量是被兩個線程訪問的,所以你需要使用一個互斥體來防止'foo'在'main'寫入時讀取它。 –