問(wèn)題描述
我對(duì) std::condition_variable
的使用有點(diǎn)困惑.我知道我必須在調(diào)用 condition_variable.wait()
之前在 mutex
上創(chuàng)建一個(gè) unique_lock
.我找不到的是我是否應(yīng)該在調(diào)用 notify_one()
或 notify_all()
之前獲取唯一鎖.
I am a bit confused about the use of std::condition_variable
. I understand I have to create a unique_lock
on a mutex
before calling condition_variable.wait()
. What I cannot find is whether I should also acquire a unique lock before calling notify_one()
or notify_all()
.
cppreference.com 上的示例相互矛盾.例如,notify_one 頁(yè)面給出了這個(gè)例子:
Examples on cppreference.com are conflicting. For example, the notify_one page gives this example:
#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;
void waits()
{
std::unique_lock<std::mutex> lk(cv_m);
std::cout << "Waiting...
";
cv.wait(lk, []{return i == 1;});
std::cout << "...finished waiting. i == 1
";
done = true;
}
void signals()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Notifying...
";
cv.notify_one();
std::unique_lock<std::mutex> lk(cv_m);
i = 1;
while (!done) {
lk.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
lk.lock();
std::cerr << "Notifying again...
";
cv.notify_one();
}
}
int main()
{
std::thread t1(waits), t2(signals);
t1.join(); t2.join();
}
這里的鎖不是為第一個(gè) notify_one()
獲取的,而是為第二個(gè) notify_one()
獲取的.查看帶有示例的其他頁(yè)面,我看到了不同的東西,主要是沒(méi)有獲取鎖.
Here the lock is not acquired for the first notify_one()
, but is acquired for the second notify_one()
. Looking though other pages with examples I see different things, mostly not acquiring the lock.
- 在調(diào)用
notify_one()
之前我可以選擇自己鎖定互斥鎖嗎,為什么我會(huì)選擇鎖定它? - 在給出的示例中,為什么第一個(gè)
notify_one()
沒(méi)有鎖定,但后續(xù)調(diào)用有鎖定.這個(gè)例子是錯(cuò)誤的還是有一些道理?
- Can I choose myself to lock the mutex before calling
notify_one()
, and why would I choose to lock it? - In the example given, why is there no lock for the first
notify_one()
, but there is for subsequent calls. Is this example wrong or is there some rationale?
推薦答案
在調(diào)用 condition_variable::notify_one()
時(shí)不需要持有鎖,但從某種意義上說(shuō)這并沒(méi)有錯(cuò)它仍然是明確定義的行為,而不是錯(cuò)誤.
You do not need to be holding a lock when calling condition_variable::notify_one()
, but it's not wrong in the sense that it's still well defined behavior and not an error.
然而,這可能是一種悲觀化",因?yàn)槿魏蔚却€程被設(shè)為可運(yùn)行(如果有)將立即嘗試獲取通知線程持有的鎖.我認(rèn)為在調(diào)用 notify_one()
或 notify_all()
時(shí)避免持有與條件變量關(guān)聯(lián)的鎖是一個(gè)很好的經(jīng)驗(yàn)法則.請(qǐng)參閱 Pthread Mutex:pthread_mutex_unlock() 消耗大量時(shí)間 例如,在調(diào)用與 notify_one()
等效的 pthread 之前釋放鎖顯著提高了性能.
However, it might be a "pessimization" since whatever waiting thread is made runnable (if any) will immediately try to acquire the lock that the notifying thread holds. I think it's a good rule of thumb to avoid holding the lock associated with a condition variable while calling notify_one()
or notify_all()
. See Pthread Mutex: pthread_mutex_unlock() consumes lots of time for an example where releasing a lock before calling the pthread equivalent of notify_one()
improved performance measurably.
請(qǐng)記住,while
循環(huán)中的 lock()
調(diào)用在某些時(shí)候是必要的,因?yàn)樾枰?while (!done)
循環(huán)條件檢查.但是對(duì)于 notify_one()
的調(diào)用不需要保持它.
Keep in mind that the lock()
call in the while
loop is necessary at some point, because the lock needs to be held during the while (!done)
loop condition check. But it doesn't need to be held for the call to notify_one()
.
2016-02-27:大型更新解決了評(píng)論中關(guān)于是否存在競(jìng)爭(zhēng)條件是鎖對(duì) notify_one()
沒(méi)有幫助的一些問(wèn)題稱呼.我知道這個(gè)更新晚了,因?yàn)檫@個(gè)問(wèn)題是在大約兩年前提出的,但我想解決@Cookie 的問(wèn)題,如果生產(chǎn)者(在這個(gè)例子中為 signals()
)調(diào)用notify_one()
就在消費(fèi)者(本例中為waits()
)能夠調(diào)用wait()
之前.
2016-02-27: Large update to address some questions in the comments about whether there's a race condition is the lock isn't help for the notify_one()
call. I know this update is late because the question was asked almost two years ago, but I'd like to address @Cookie's question about a possible race condition if the producer (signals()
in this example) calls notify_one()
just before the consumer (waits()
in this example) is able to call wait()
.
關(guān)鍵是 i
發(fā)生了什么 - 這是實(shí)際指示消費(fèi)者是否有工作"要做的對(duì)象.condition_variable
只是一種讓消費(fèi)者有效等待 i
更改的機(jī)制.
The key is what happens to i
- that's the object that actually indicates whether or not the consumer has "work" to do. The condition_variable
is just a mechanism to let the consumer efficiently wait for a change to i
.
生產(chǎn)者在更新i
時(shí)需要持有鎖,消費(fèi)者在檢查i
和調(diào)用condition_variable::wait()時(shí)必須持有鎖
(如果它需要等待).在這種情況下,關(guān)鍵是當(dāng)消費(fèi)者執(zhí)行此檢查和等待時(shí),它必須是持有鎖的同一實(shí)例(通常稱為臨界區(qū)).由于臨界區(qū)在生產(chǎn)者更新 i
和消費(fèi)者檢查并等待 i
時(shí)被保留,所以 i
沒(méi)有機(jī)會(huì)在消費(fèi)者檢查 i
和調(diào)用 condition_variable::wait()
之間切換.這是正確使用條件變量的關(guān)鍵.
The producer needs to hold the lock when updating i
, and the consumer must hold the lock while checking i
and calling condition_variable::wait()
(if it needs to wait at all). In this case, the key is that it must be the same instance of holding the lock (often called a critical section) when the consumer does this check-and-wait. Since the critical section is held when the producer updates i
and when the consumer checks-and-waits on i
, there is no opportunity for i
to change between when the consumer checks i
and when it calls condition_variable::wait()
. This is the crux for a proper use of condition variables.
C++ 標(biāo)準(zhǔn)規(guī)定,當(dāng)使用謂詞調(diào)用時(shí),condition_variable::wait() 的行為如下(如本例所示):
The C++ standard says that condition_variable::wait() behaves like the following when called with a predicate (as in this case):
while (!pred())
wait(lock);
消費(fèi)者檢查i
時(shí)可能出現(xiàn)兩種情況:
There are two situations that can occur when the consumer checks i
:
如果
i
為0,則消費(fèi)者調(diào)用cv.wait()
,則i
在wait(lock)
部分實(shí)現(xiàn)被調(diào)用 - 正確使用鎖確保了這一點(diǎn).在這種情況下,生產(chǎn)者沒(méi)有機(jī)會(huì)在其while
循環(huán)中調(diào)用condition_variable::notify_one()
直到消費(fèi)者調(diào)用cv.wait(lk,[]{return i == 1;})
(并且wait()
調(diào)用已經(jīng)完成了正確捕捉"通知所需的一切 -wait()
在它完成之前不會(huì)釋放鎖).所以在這種情況下,消費(fèi)者不會(huì)錯(cuò)過(guò)通知.
if
i
is 0 then the consumer callscv.wait()
, theni
will still be 0 when thewait(lock)
part of the implementation is called - the proper use of the locks ensures that. In this case the producer has no opportunity to call thecondition_variable::notify_one()
in itswhile
loop until after the consumer has calledcv.wait(lk, []{return i == 1;})
(and thewait()
call has done everything it needs to do to properly 'catch' a notify -wait()
won't release the lock until it has done that). So in this case, the consumer cannot miss the notification.
如果當(dāng)消費(fèi)者調(diào)用cv.wait()
時(shí)i
已經(jīng)為1,則wait(lock)
部分永遠(yuǎn)不會(huì)調(diào)用實(shí)現(xiàn),因?yàn)?while (!pred())
測(cè)試將導(dǎo)致內(nèi)部循環(huán)終止.在這種情況下,何時(shí)調(diào)用 notify_one() 無(wú)關(guān)緊要 - 消費(fèi)者不會(huì)阻塞.
if i
is already 1 when the consumer calls cv.wait()
, the wait(lock)
part of the implementation will never be called because the while (!pred())
test will cause the internal loop to terminate. In this situation it doesn't matter when the call to notify_one() occurs - the consumer will not block.
此處的示例確實(shí)具有額外的復(fù)雜性,即使用 done
變量向生產(chǎn)者線程發(fā)信號(hào)通知消費(fèi)者已識(shí)別 i == 1
,但我不要認(rèn)為這根本不會(huì)改變分析,因?yàn)閷?duì) done
的所有訪問(wèn)(用于讀取和修改)都是在涉及 i
和condition_variable
.
The example here does have the additional complexity of using the done
variable to signal back to the producer thread that the consumer has recognized that i == 1
, but I don't think this changes the analysis at all because all of the access to done
(for both reading and modifying) are done while in the same critical sections that involve i
and the condition_variable
.
如果你看看@eh9 指出的問(wèn)題,同步使用 std::atomic 和 std::condition_variable 不可靠,您會(huì)看到競(jìng)爭(zhēng)條件.但是,該問(wèn)題中發(fā)布的代碼違反了使用條件變量的基本規(guī)則之一:在執(zhí)行檢查和等待時(shí),它不包含單個(gè)臨界區(qū).
If you look at the question that @eh9 pointed to, Sync is unreliable using std::atomic and std::condition_variable, you will see a race condition. However, the code posted in that question violates one of the fundamental rules of using a condition variable: It does not hold a single critical section when performing a check-and-wait.
在該示例中,代碼如下所示:
In that example, the code looks like:
if (--f->counter == 0) // (1)
// we have zeroed this fence's counter, wake up everyone that waits
f->resume.notify_all(); // (2)
else
{
unique_lock<mutex> lock(f->resume_mutex);
f->resume.wait(lock); // (3)
}
您會(huì)注意到#3 處的wait()
是在保持f->resume_mutex
的同時(shí)執(zhí)行的.但是在第 1 步檢查 wait()
是否有必要沒(méi)有在持有該鎖的情況下完成(更不用說(shuō)連續(xù)檢查和 -等待),這是正確使用條件變量的要求).我相信對(duì)該代碼片段有問(wèn)題的人認(rèn)為,由于 f->counter
是一個(gè) std::atomic
類型,這將滿足要求.但是,std::atomic
提供的原子性不會(huì)擴(kuò)展到對(duì) f->resume.wait(lock)
的后續(xù)調(diào)用.在此示例中,在檢查 f->counter
時(shí)(步驟 1)和調(diào)用 wait()
時(shí)(步驟 3)之間存在競(jìng)爭(zhēng).
You will notice that the wait()
at #3 is performed while holding f->resume_mutex
. But the check for whether or not the wait()
is necessary at step #1 is not done while holding that lock at all (much less continuously for the check-and-wait), which is a requirement for proper use of condition variables). I believe that the person who has the problem with that code snippet thought that since f->counter
was a std::atomic
type this would fulfill the requirement. However, the atomicity provided by std::atomic
doesn't extend to the subsequent call to f->resume.wait(lock)
. In this example, there is a race between when f->counter
is checked (step #1) and when the wait()
is called (step #3).
這個(gè)問(wèn)題的例子中不存在那個(gè)種族.
That race does not exist in this question's example.
這篇關(guān)于在調(diào)用condition_variable.notify_one() 之前是否必須獲取鎖?的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!