2017-10-07 254 views
0

我試圖使用C++11std::condition_variable來處理UI線程&工作者線程之間的數據事務。C++:如何在UI線程和工作者std :: thread之間使用std :: condition_variable

情況:
m_calculated_value是其中複雜的邏輯後算出的值。這是UI線程觸發事件所必需的。 UI線程調用MyClass::GetCalculatedValue來獲取m_calculated_value的值,該值需要由工作程序線程函數MyClass::ThreadFunctionToCalculateValue進行計算。

代碼:

std::mutex    m_mutex; 
std::condition_variable m_my_condition_variable; 
bool     m_value_ready; 
unsigned int   m_calculated_value; 


// Gets called from UI thread 
unsigned int MyClass::GetCalculatedValue() { 

    std::unique_lock<std::mutex> lock(m_mutex); 
    m_value_ready = false; 

    m_my_condition_variable.wait(lock, std::bind(&MyClass::IsValueReady, this)); 

    return m_calculated_value; 
} 


bool MyClass::IsValueReady() { 

    return m_value_ready; 
} 

// Gets called from an std::thread or worker thread 
void MyClass::ThreadFunctionToCalculateValue() { 

    std::unique_lock<std::mutex> lock(m_mutex); 

    m_calculated_value = ComplexLogicToCalculateValue(); 
    m_value_ready = true; 

    m_my_condition_variable.notify_one(); 
} 

問題:
但問題是,m_my_condition_variable.wait永遠不會返回。

問:
我在做什麼錯在這裏?

讓UI線程等待來自工作線程的條件變量信號是否正確?如何擺脫由於工作線程函數中的錯誤而導致condition_variable從不觸發的情況?有什麼辦法可以在這裏使用暫停嗎?

試圖瞭解它是如何工作:
我在許多例子中看到他們用while循環檢查condition_var.wait周圍的布爾變量的狀態。什麼是變量的循環點? 當我從其他線程調用notify_one時,我能期待m_my_condition_variable返回wait嗎?

+0

你指的這些例子並沒有使用'等待()'與謂詞過載。因此他們基本上重新實現了那一個。無論如何,對於你的用例,你應該使用未來,而不是條件變量。 – Ext3h

+0

當MyClass :: GetCalculatedValue返回時,我需要'm_calculated_value'準備好所需的值。這看起來像線程之間的一種典型信號情況,告訴數據已準備就緒。 @ Wxt3h你不這麼認爲嗎?未來將如何幫助這裏?你能再詳細一點嗎? –

+0

我發現許多錯誤,但不會導致你的症狀。您提出了多個問題,但未能提供[mcve]。有關於如何使用條件變量的SO Q&A,我建議閱讀這些內容並按照他們的說法做。 – Yakk

回答

0

在這裏這個例子看看:

http://en.cppreference.com/w/cpp/thread/condition_variable

更改在下面的示例代碼中的註釋指出有問題的代碼。您可能要考慮使用與cppreference.com示例中使用的相同的「握手」來在可以安全計算新值(UI線程有wait/notify,工作線程有通知/等待)時進行同步。

在條件變量等待之前,鎖需要被鎖定。等待會解鎖,等待通知,然後鎖定並使用謂詞功能,檢查是否準備就緒,如果尚未準備好(虛擬喚醒),請重複此循環。

在notify_one之前,鎖應該被解鎖,否則等待會被喚醒,但是無法獲得鎖(因爲它仍然被鎖定)。

std::mutex    m_mutex; 
std::condition_variable m_my_condition_variable; 
bool     m_value_ready = false; // init to false 
unsigned int   m_calculated_value; 


// Gets called from UI thread 
unsigned int MyClass::GetCalculatedValue() { 
    std::unique_lock<std::mutex> lock(m_mutex); 
    m_my_condition_variable.wait(lock, std::bind(&MyClass::IsValueReady, this)); 
    m_value_ready = false; // don't change until after wait 
    return m_calculated_value; 
} // auto unlock after leaving function scope 

bool MyClass::IsValueReady() { 

    return m_value_ready; 
} 

// Gets called from an std::thread or worker thread 
void MyClass::ThreadFunctionToCalculateValue() { 
    std::unique_lock<std::mutex> lock(m_mutex); 
    m_calculated_value = ComplexLogicToCalculateValue(); 
    m_value_ready = true; 
    lock.unlock();   // unlock before notify 
    m_my_condition_variable.notify_one(); 
} 

或替代:

// Gets called from an std::thread or worker thread 
void MyClass::ThreadFunctionToCalculateValue() { 

    { // auto unlock after leaving block scope 
     std::lock_guard<std::mutex> lock(m_mutex); 
     m_calculated_value = ComplexLogicToCalculateValue(); 
     m_value_ready = true; 
    } // unlock occurs here 
    m_my_condition_variable.notify_one(); 
} 
3

什麼是最有可能發生的: 您的工作線程擁有並持有互斥量,直到完成計算。主線程必須等到它可以獲取鎖定。工人將傳達出CV 它釋放鎖(在析構函數)之前,此時沒有其他線程將要等待條件變量的可能已被收購,它仍然被通知線程佔用的鎖。因此,另一個線程在得到通知的時候從來沒有機會等待條件變量,因爲它在通知事件發生後才設法獲取鎖,導致它無限等待。

的解決方案將是解除鎖定採集MyClass中:: ThreadFunctionToCalculateValue(),它不要求有在所有的,或至少,不應該。

但不管怎麼說,你爲什麼要重新發明輪子呢?對於這樣的問題,std::future已創建:

auto future = std::async(std::launch::async, ComplexLogicToCalculateValue); 
bool is_ready = future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; 
auto result = future.get(); 

在這裏,你可以很容易地定義超時,您不必擔心condition_variables和一致好評。

當我從其他線程調用notify_one時,我不希望m_my_condition_variable退出等待狀態嗎?

No,不是唯一的。虛假喚醒仍可能發生。

+0

好的。那麼我不應該在這裏使用condition_variable。感謝您的建議 –

+0

我認爲可以處理虛假喚醒?如果一個'condition_variable'至少可以用於這個場景,那麼我可以嘗試處理虛假喚醒。 –

+0

當然可以。但爲什麼麻煩的是,如果手頭有更清潔的解決方案呢? – Jodocus

相關問題