我是多執行緒編程的新手,我想知道在嘗試使用多個生產者實作消費者生產者問題時是否有一些最佳實踐。
這是我當前的實作,它似乎作業正常,我的主要疑問是關于使用mtx.lock()(例如,如果我應該使用 alock_guard代替,如果這樣做有什么好處)。
此外,可以放在g_mtx.unlock()前面g_cv.notify_one();嗎?我真的不明白誰獲得互斥鎖以及何時獲得;我的理解是,消費者在檢查 conditional_variable 條件(并且 is )g_mtx之前獲取,然后釋放它,然后當它收到通知時,它重新檢查變數,如果是,它重新獲取互斥鎖。這樣對嗎?g_cv.waitfalsetrue
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
std::atomic<int> g_n = 0;
static std::condition_variable g_cv;
static std::mutex g_mtx;
static bool g_notified=false;
void consumerFunction(){
while(g_n==0);
while(g_n>0){
std::unique_lock<std::mutex> g_lock(g_mtx);
std::cout << "[CONSUMER] Waiting for notification..." << std::endl;
g_cv.wait(g_lock, []{
std::cout << "[CONSUMER] Notification received!" << std::endl;
return g_notified;
});
g_n-=1;
std::cout << "[CONSUMER] One less Producer!" << std::endl;
g_notified = false;
}
std::cout << "[CONSUMER] CONSUMER FUNCTION HAS COMPLETED ITS WORK" << std::endl;
}
void producerFunction(){
uint32_t count;
for(uint32_t i = 0; i < 10; i ){
// Count until 10...
std::this_thread::sleep_for(std::chrono::seconds(1));
}
g_mtx.lock();
g_notified=true;
g_mtx.unlock();
std::cout << "[PRODUCER] Notifying..." << std::endl;
g_cv.notify_one();
}
int main(){
std::thread consumer([]{consumerFunction();});
g_n=4;
std::vector<std::thread> threads;
for(int i = 0; i <g_n; i ){
std::this_thread::sleep_for(std::chrono::seconds(5));
threads.emplace_back([]{producerFunction();});
}
consumer.join();
for(auto& t: threads){
if(t.joinable()){
t.join();
}
}
return 0;
}
注意:如果您嘗試在您的環境中編譯此代碼,您應該在除錯模式下構建它以避免任何優化(不確定是否是這種情況,但在定義虛擬生產者函式之前發生在我身上)。
謝謝
uj5u.com熱心網友回復:
總的來說,這對我來說看起來不錯。
旋轉等待while(g_n==0);有點貴,但我想這是示例的一部分,這部分不會在生產中使用。否則,最好用被動等待方法(通常是另一個等待條件或信號量)來代替它。
例如,如果我應該改用 lock_guard,那么這樣做有什么好處
使用它可能會更短一些,并且可能更安全一些,但它不會對這個特定代碼產生重大影響。守衛可以很好地確保即使在復雜/意外情況下(例如發生例外時)也能釋放鎖(這里不多)。是否應該使用它是見仁見智的問題。
可以放在
g_mtx.unlock()前面嗎g_cv.notify_one();
使用當前代碼,g_cv.wait可以虛假地喚醒并檢查g_mtx.unlock()被呼叫之后和之前的條件g_cv.notify_one()。如果是這樣,則呼叫檢查 lambda 并將回傳 true,列印可以顯示,然后可以在呼叫之前釋放并再次獲取g_cv.wait鎖,這將在進入睡眠模式之前釋放鎖。之前的所有這些g_cv.notify_one()都被呼叫了!話雖如此,對 的呼叫g_cv.notify_one()將無法完全喚醒對 的新呼叫g_cv.wait。實際上,通知會像虛假喚醒一樣喚醒等待,但該g_notified值將為 false,因此等待功能不會完成。因此,這很好。
如果將通知放在解鎖之前,則等待函式無法完全喚醒,因為需要在呼叫 check lambda 之前獲取鎖。在某些平臺上,它可能會導致等待執行緒由于通知而喚醒,然后立即等待獲取互斥體,從而導致不必要的背景關系切換。參考C 檔案:
通知執行緒不需要持有與等待執行緒持有的互斥鎖相同的互斥鎖;實際上這樣做是一種悲觀,因為被通知的執行緒會立即再次阻塞,等待通知執行緒釋放鎖。但是,一些實作(特別是 pthread 的許多實作)認識到這種情況并通過將等待執行緒從條件變數佇列直接轉移到通知呼叫中的互斥體佇列而不喚醒它來避免這種“快點等待”場景向上。
我的理解是,消費者在 g_cv.wait 之前獲取 g_mtx,然后在檢查 conditional_variable 條件(并且為假)時釋放它,然后當它收到通知時,它重新檢查變數,如果為真,它重新獲取互斥鎖。這樣對嗎?
是的。g_cv.wait原子釋放g_mtx并使執行緒進入睡眠狀態。但是,AFAIK,在呼叫檢查 lambda之前獲取互斥鎖。這很重要,因為g_notified受到鎖的保護并且必須在鎖定的部分中訪問。更具體地說,g_notified由于獲取/釋放互斥鎖產生的隱式記憶體屏障,互斥鎖確保讀取是正確的。如果檢查 lambda 回傳 false,則釋放互斥鎖。如果是這樣,則執行緒進入睡眠狀態。最后,所有的訪問g_notified都在代碼中受到保護(除了不需要保護的初始化)。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/515307.html
標籤:C 多线程生产者-消费者
下一篇:在Lua中注冊回呼
