php 语法 条件变量,C ++核心准则:注意条件变量的陷阱
今天,我寫了一篇關(guān)于條件變量的恐怖文章。您應(yīng)該意識(shí)到條件變量的這一問題。C ++核心準(zhǔn)則CP 42僅聲明:“不要無條件等待”。
等待!條件變量支持一個(gè)非常簡單的概念。一個(gè)線程準(zhǔn)備一些東西并發(fā)送通知,另一個(gè)線程正在等待。為什么不能這么危險(xiǎn)?好吧,讓我們從今天的唯一規(guī)則開始。
這是該規(guī)則的基本原理:“沒有條件的等待可能會(huì)錯(cuò)過喚醒或僅醒來就發(fā)現(xiàn)沒有工作要做?!?這意味著什么?條件變量可能是兩個(gè)非常嚴(yán)重的問題的受害者:喚醒丟失和偽喚醒。關(guān)于條件變量的關(guān)鍵問題是它們沒有記憶(memory)。
在我向您介紹此問題之前,請(qǐng)先讓我正確進(jìn)行操作。這是模式,如何使用條件變量。
// conditionVariables.cpp
#include
#include
#include
std::mutex mutex_;
std::condition_variable condVar;
bool dataReady{false};
void waitingForWork(){
std::cout << "Waiting " << std::endl;
std::unique_lock<:mutex> lck(mutex_);
condVar.wait(lck, []{ return dataReady; });? // (4)
std::cout << "Running " << std::endl;
}
void setDataReady(){
{
std::lock_guard<:mutex> lck(mutex_);
dataReady = true;
}
std::cout << "Data prepared" << std::endl;
condVar.notify_one();? ? ? ? ? ? ? ? ? ? ? ? // (3)
}
int main(){
std::cout << std::endl;
std::thread t1(waitingForWork);? ? ? ? ? ? ? // (1)
std::thread t2(setDataReady);? ? ? ? ? ? ? ? // (2)
t1.join();
t2.join();
std::cout << std::endl;
}
同步如何工作?該程序有兩個(gè)子線程:t1和t2。他們?在第(1和2)行中獲得了工作包?waitingForWork?和setDataRead。setDataReady?通知-使用條件變量condVar?-即它與工作的準(zhǔn)備完成:condVar.notify_one()?(第3行)。在持有鎖的同時(shí),線程t1?等待其通知:condVar.wait(lck,[] {return dataReady;})(第4行)。發(fā)送者和接收者需要鎖。對(duì)于發(fā)送者,則為std :: lock_guard?足夠了,因?yàn)樗徽{(diào)用一次鎖定和解鎖。對(duì)于接收器,std :: unique_lock?是必需的,因?yàn)樗ǔ?huì)頻繁地鎖定和解鎖其互斥鎖。
這是程序的輸出。
也許您在想:為什么您需要一個(gè)謂詞才能進(jìn)行wait調(diào)用,因?yàn)槟梢栽?沒有謂詞的情況下調(diào)用wait?對(duì)于如此簡單的線程同步,此工作流程似乎過于復(fù)雜。
現(xiàn)在我們回到memory的丟失中,這兩種現(xiàn)象稱為丟失喚醒和偽喚醒。
丟失的喚醒和虛假的喚醒
喚醒丟失:喚醒丟失的現(xiàn)象是發(fā)送方在接收方進(jìn)入其等待狀態(tài)之前發(fā)送其通知。結(jié)果是通知丟失。C ++標(biāo)準(zhǔn)描述條件變量作為同時(shí)同步機(jī)制:"The condition_variable class is a synchronisation primitive that can be used to block a thread, or multiple threads?at the same time, ..."。因此通知丟失了,接收方正在等待,并且等待...。
虛假喚醒:盡管沒有發(fā)送通知,但接收器可能會(huì)喚醒。至少,POSIX Threads?和Windows API可能成為這些現(xiàn)象的受害者。
為了不成為這兩個(gè)問題的受害者,您必須使用其他謂詞作為記憶(memory)。或按規(guī)則規(guī)定是附加條件。如果您不相信,這里就是等待工作流程。
等待工作流程
在等待的初始處理中,線程將鎖定互斥鎖,然后檢查謂詞[] {return dataReady;。}。
如果謂詞的調(diào)用評(píng)估為
true:線程繼續(xù)其工作。
false:condVar.wait()?解鎖互斥鎖并將線程置于等待(阻塞)狀態(tài)
如果condition_variable?condVar處于等待狀態(tài)并收到通知或虛假喚醒,則會(huì)發(fā)生以下步驟。
線程被解除阻止,并將重新獲取互斥鎖。
線程檢查謂詞。
如果謂詞的調(diào)用評(píng)估為
true:線程繼續(xù)其工作。
false:condVar.wait()解鎖互斥鎖,并將線程置于等待(阻塞)狀態(tài)。
復(fù)雜!對(duì)?你不相信我嗎
沒有謂詞
如果我從上一個(gè)示例中刪除謂詞,將會(huì)發(fā)生什么?
// conditionVariableWithoutPredicate.cpp
#include
#include
#include
std::mutex mutex_;
std::condition_variable condVar;
void waitingForWork(){
std::cout << "Waiting " << std::endl;
std::unique_lock<:mutex> lck(mutex_);
condVar.wait(lck);? ? ? ? ? ? ? ? ? ? ? // (1)
std::cout << "Running " << std::endl;
}
void setDataReady(){
std::cout << "Data prepared" << std::endl;
condVar.notify_one();? ? ? ? ? ? ? ? ? // (2)
}
int main(){
std::cout << std::endl;
std::thread t1(waitingForWork);
std::thread t2(setDataReady);
t1.join();
t2.join();
std::cout << std::endl;
}
現(xiàn)在,第(1)行中的wait調(diào)用不使用謂詞,并且同步看起來非常容易。遺憾地說,但現(xiàn)在的程序有一個(gè)競爭條件,你可以在第一個(gè)執(zhí)行看到。屏幕截圖顯示了死鎖。
發(fā)送方在接收方能夠接收之前,在第(1)行(condVar.notify_one())中發(fā)送其通知;因此,接收器將永遠(yuǎn)休眠。
好的,教訓(xùn)是艱難的。謂詞是必要的,但必須有一種方法可以簡化程序的條件。
原子謂詞
也許您已經(jīng)看到了。變量dataReady只是一個(gè)布爾值。我們應(yīng)該將其設(shè)為原子布爾值,并因此擺脫發(fā)送方上的互斥量。
我們來了:
// conditionVariableAtomic.cpp
#include
#include
#include
#include
std::mutex mutex_;
std::condition_variable condVar;
std::atomic dataReady{false};
void waitingForWork(){
std::cout << "Waiting " << std::endl;
std::unique_lock<:mutex> lck(mutex_);
condVar.wait(lck, []{ return dataReady.load(); });? // (1)
std::cout << "Running " << std::endl;
}
void setDataReady(){
dataReady = true;
std::cout << "Data prepared" << std::endl;
condVar.notify_one();
}
int main(){
std::cout << std::endl;
std::thread t1(waitingForWork);
std::thread t2(setDataReady);
t1.join();
t2.join();
std::cout << std::endl;
}
與第一個(gè)版本相比,該程序非常簡單,因?yàn)閐ataReady不必由互斥量保護(hù)。程序再次處于競爭狀態(tài),可能導(dǎo)致死鎖。為什么?dataReady是原子的!是的,但是第(1)行中的wait表達(dá)式(condVar.wait(lck,[] {return dataReady.load();});)比看起來復(fù)雜得多。
wait表達(dá)式等效于以下四行:
std :: unique_lock < std ::互斥> lck(mutex_);
while(! [] { return dataReady.load();}(){
//時(shí)間窗口(1)? ? condVar.wait(lck);}
即使將dataReady設(shè)為原子,也必須在互斥鎖下對(duì)其進(jìn)行修改;如果不是,則可能會(huì)發(fā)布對(duì)等待線程的修改,但不能正確同步。這種競爭狀況可能會(huì)導(dǎo)致死鎖。這是什么意思:已發(fā)布但未正確同步。讓我們仔細(xì)看一下前面的代碼片段,并假設(shè)數(shù)據(jù)是原子的,并且不受互斥對(duì)象Mutex_的保護(hù)。
讓我假設(shè)在條件變量condVar在等待表達(dá)式中但不在等待狀態(tài)時(shí)發(fā)送通知。這意味著線程的執(zhí)行位于注釋時(shí)間窗口(第1行)所在行的源代碼片段中。結(jié)果是通知丟失。之后,線程返回等待狀態(tài),并且可能永遠(yuǎn)休眠。
如果dataReady受互斥鎖保護(hù),則不會(huì)發(fā)生這種情況。由于與互斥鎖同步,因此僅在條件變量(因此接收方線程)處于等待狀態(tài)時(shí)才發(fā)送通知。
多么恐怖的故事?有沒有可能使用開始的程序conditionVariables.cpp更容易?不,不是帶有條件變量的,但是您可以使用promise和future配對(duì)來使工作完成
總結(jié)
以上是生活随笔為你收集整理的php 语法 条件变量,C ++核心准则:注意条件变量的陷阱的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何实现php自动备份数据库,使用php
- 下一篇: php动态网页转换成html,怎么把动态