問題描述
我對 std::condition_variable
的使用有點困惑.我知道我必須在調用 condition_variable.wait()
之前在 mutex
上創建一個 unique_lock
.我找不到的是我是否應該在調用 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 頁面給出了這個例子:
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();
}
這里的鎖不是為第一個 notify_one()
獲取的,而是為第二個 notify_one()
獲取的.查看帶有示例的其他頁面,我看到了不同的東西,主要是沒有獲取鎖.
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.
- 在調用
notify_one()
之前我可以選擇自己鎖定互斥鎖嗎,為什么我會選擇鎖定它? - 在給出的示例中,為什么第一個
notify_one()
沒有鎖定,但后續調用有鎖定.這個例子是錯誤的還是有一些道理?
- 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?
推薦答案
在調用 condition_variable::notify_one()
時不需要持有鎖,但從某種意義上說這并沒有錯它仍然是明確定義的行為,而不是錯誤.
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.
然而,這可能是一種悲觀化",因為任何等待線程被設為可運行(如果有)將立即嘗試獲取通知線程持有的鎖.我認為在調用 notify_one()
或 notify_all()
時避免持有與條件變量關聯的鎖是一個很好的經驗法則.請參閱 Pthread Mutex:pthread_mutex_unlock() 消耗大量時間 例如,在調用與 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.
請記住,while
循環中的 lock()
調用在某些時候是必要的,因為需要在 while (!done)
循環條件檢查.但是對于 notify_one()
的調用不需要保持它.
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:大型更新解決了評論中關于是否存在競爭條件是鎖對 notify_one()
沒有幫助的一些問題稱呼.我知道這個更新晚了,因為這個問題是在大約兩年前提出的,但我想解決@Cookie 的問題,如果生產者(在這個例子中為 signals()
)調用notify_one()
就在消費者(本例中為waits()
)能夠調用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()
.
關鍵是 i
發生了什么 - 這是實際指示消費者是否有工作"要做的對象.condition_variable
只是一種讓消費者有效等待 i
更改的機制.
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
.
生產者在更新i
時需要持有鎖,消費者在檢查i
和調用condition_variable::wait()時必須持有鎖
(如果它需要等待).在這種情況下,關鍵是當消費者執行此檢查和等待時,它必須是持有鎖的同一實例(通常稱為臨界區).由于臨界區在生產者更新 i
和消費者檢查并等待 i
時被保留,所以 i
沒有機會在消費者檢查 i
和調用 condition_variable::wait()
之間切換.這是正確使用條件變量的關鍵.
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++ 標準規定,當使用謂詞調用時,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);
消費者檢查i
時可能出現兩種情況:
There are two situations that can occur when the consumer checks i
:
如果
i
為0,則消費者調用cv.wait()
,則i
在wait(lock)
部分實現被調用 - 正確使用鎖確保了這一點.在這種情況下,生產者沒有機會在其while
循環中調用condition_variable::notify_one()
直到消費者調用cv.wait(lk,[]{return i == 1;})
(并且wait()
調用已經完成了正確捕捉"通知所需的一切 -wait()
在它完成之前不會釋放鎖).所以在這種情況下,消費者不會錯過通知.
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.
如果當消費者調用cv.wait()
時i
已經為1,則wait(lock)
部分永遠不會調用實現,因為 while (!pred())
測試將導致內部循環終止.在這種情況下,何時調用 notify_one() 無關緊要 - 消費者不會阻塞.
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.
此處的示例確實具有額外的復雜性,即使用 done
變量向生產者線程發信號通知消費者已識別 i == 1
,但我不要認為這根本不會改變分析,因為對 done
的所有訪問(用于讀取和修改)都是在涉及 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 指出的問題,同步使用 std::atomic 和 std::condition_variable 不可靠,您會看到競爭條件.但是,該問題中發布的代碼違反了使用條件變量的基本規則之一:在執行檢查和等待時,它不包含單個臨界區.
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)
}
您會注意到#3 處的wait()
是在保持f->resume_mutex
的同時執行的.但是在第 1 步檢查 wait()
是否有必要沒有在持有該鎖的情況下完成(更不用說連續檢查和 -等待),這是正確使用條件變量的要求).我相信對該代碼片段有問題的人認為,由于 f->counter
是一個 std::atomic
類型,這將滿足要求.但是,std::atomic
提供的原子性不會擴展到對 f->resume.wait(lock)
的后續調用.在此示例中,在檢查 f->counter
時(步驟 1)和調用 wait()
時(步驟 3)之間存在競爭.
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).
這個問題的例子中不存在那個種族.
That race does not exist in this question's example.
這篇關于在調用condition_variable.notify_one() 之前是否必須獲取鎖?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!