C++ 在调用condition_variable.notify_one() 之前是否必须获取锁?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17101922/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Do I have to acquire lock before calling condition_variable.notify_one()?
提问by Peter Smit
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()
.
我对std::condition_variable
. 我明白我必须创建unique_lock
一个mutex
调用之前condition_variable.wait()
。我找不到的是我是否还应该在调用notify_one()
或之前获取一个唯一的锁notify_all()
。
Examples on cppreference.comare conflicting. For example, the notify_one pagegives this example:
cppreference.com上的示例相互矛盾。例如,notify_one 页面给出了这个例子:
#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... \n";
cv.wait(lk, []{return i == 1;});
std::cout << "...finished waiting. i == 1\n";
done = true;
}
void signals()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Notifying...\n";
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...\n";
cv.notify_one();
}
}
int main()
{
std::thread t1(waits), t2(signals);
t1.join(); t2.join();
}
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?
- 我可以在调用之前选择自己锁定互斥
notify_one()
锁吗,为什么我会选择锁定它? - 在给出的示例中,为什么第一个没有锁定
notify_one()
,但后续调用有锁定。这个例子是错误的还是有一些理由?
采纳答案by Michael Burr
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.
调用 时您不需要持有锁condition_variable::notify_one()
,但从某种意义上说它仍然是明确定义的行为而不是错误,这并没有错。
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 timefor an example where releasing a lock before calling the pthread equivalent of notify_one()
improved performance measurably.
但是,这可能是一种“悲观化”,因为任何等待线程变为可运行的(如果有)都会立即尝试获取通知线程持有的锁。我认为在调用notify_one()
or 时避免持有与条件变量关联的锁是一个很好的经验法则notify_all()
。请参阅Pthread Mutex:pthread_mutex_unlock() 消耗大量时间的示例,其中在调用 pthread 等价物之前释放锁,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()
.
请记住,循环中的lock()
调用在while
某些时候是必要的,因为在while (!done)
循环条件检查期间需要保持锁定。但它不需要为调用而保留notify_one()
。
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()
.
2016 年 2 月 27 日:大型更新以解决评论中关于是否存在竞争条件的一些问题,即锁定对notify_one()
呼叫没有帮助。我知道这个更新晚了,因为这个问题是在大约两年前提出的,但是如果生产者(signals()
在这个例子中)notify_one()
在消费者(waits()
在这个例子中)之前调用,我想解决@Cookie 关于可能的竞争条件的问题能打电话wait()
。
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
- 那是实际指示消费者是否有“工作”要做的对象。这condition_variable
只是一种让消费者有效等待更改的机制i
。
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.
生产者在更新时需要持有锁i
,消费者在检查i
和调用时必须持有锁condition_variable::wait()
(如果它需要等待的话)。在这种情况下,关键是当消费者执行此检查和等待时,它必须是持有锁(通常称为临界区)的同一个实例。由于临界区在生产者更新i
和消费者检查并等待时保持i
,因此没有机会i
在消费者检查i
和调用之间进行更改condition_variable::wait()
。这是正确使用条件变量的关键。
The C++ standard says that condition_variable::wait() behaves like the following when called with a predicate (as in this case):
C++ 标准规定,当使用谓词调用时,condition_variable::wait() 的行为如下(在本例中):
while (!pred())
wait(lock);
There are two situations that can occur when the consumer checks i
:
消费者检查时可能会发生两种情况i
:
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.if
i
is already 1 when the consumer callscv.wait()
, thewait(lock)
part of the implementation will never be called because thewhile (!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.
如果
i
为 0,则消费者调用cv.wait()
,则在调用实现部分i
时仍为 0wait(lock)
- 正确使用锁可确保这一点。在这种情况下,生产者没有机会condition_variable::notify_one()
在其while
循环中调用直到消费者调用之后cv.wait(lk, []{return i == 1;})
(并且wait()
调用已经完成了正确“捕获”通知所需的一切 -wait()
在完成之前不会释放锁)。所以在这种情况下,消费者不能错过通知。如果
i
在消费者调用时已经为 1cv.wait()
,wait(lock)
则永远不会调用该部分实现,因为while (!pred())
测试将导致内部循环终止。在这种情况下,何时调用 notify_one() 无关紧要 - 消费者不会阻塞。
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
.
此处的示例确实具有额外的复杂性,即使用done
变量向生产者线程发回信号,消费者已认识到这一点i == 1
,但我认为这根本不会改变分析,因为所有访问done
(读取和修改) ) 是在涉及i
和 的相同关键部分中完成的condition_variable
。
If you look at the question that @eh9 pointed to, Sync is unreliable using std::atomic and std::condition_variable, you willsee 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.
如果您查看@eh9 指出的问题,使用 std::atomic 和 std::condition_variable Sync is unreliable,您将看到竞争条件。但是,该问题中发布的代码违反了使用条件变量的基本规则之一:在执行检查和等待时,它不包含单个临界区。
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)
}
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 notdone 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).
您会注意到wait()
在 #3 处执行的同时按住f->resume_mutex
。但是wait()
在第 1 步检查是否有必要在持有该锁时根本没有完成(对于检查和等待来说更不用说连续),这是正确使用条件变量的要求)。我相信对该代码片段有问题的人认为,因为f->counter
是一种std::atomic
类型,这将满足要求。但是,由 提供的原子性std::atomic
不会扩展到对 的后续调用f->resume.wait(lock)
。在这个例子中,在f->counter
检查时间(第 1 步)和wait()
调用时间(第 3 步)之间存在竞争。
That race does not exist in this question's example.
在这个问题的例子中不存在那个种族。
回答by Matth?us Brandl
Situation
情况
Using vc10 and Boost 1.56 I implemented a concurrent queue pretty much like this blog postsuggests. The author unlocks the mutex to minimize contention, i.e., notify_one()
is called with the mutex unlocked:
我使用 vc10 和 Boost 1.56 实现了一个并发队列,就像这篇博文所建议的那样。作者解锁互斥锁以最小化争用,即notify_one()
在互斥锁解锁的情况下调用:
void push(const T& item)
{
std::unique_lock<std::mutex> mlock(mutex_);
queue_.push(item);
mlock.unlock(); // unlock before notificiation to minimize mutex contention
cond_.notify_one(); // notify one waiting thread
}
Unlocking the mutex is backed by an example in the Boost documentation:
Boost 文档中的示例支持解锁互斥锁:
void prepare_data_for_processing()
{
retrieve_data();
prepare_data();
{
boost::lock_guard<boost::mutex> lock(mut);
data_ready=true;
}
cond.notify_one();
}
Problem
问题
Still this led to the following erratic behaviour:
这仍然导致以下不稳定的行为:
- while
notify_one()
has notbeen called yetcond_.wait()
can still be interrupted viaboost::thread::interrupt()
- once
notify_one()
was called for the first timecond_.wait()
deadlocks; the wait cannot be ended byboost::thread::interrupt()
orboost::condition_variable::notify_*()
anymore.
- 而
notify_one()
一直没有被调用但cond_.wait()
仍然可以通过中断boost::thread::interrupt()
- 曾经
notify_one()
第一次被调用cond_.wait()
死锁;等待不能结束boost::thread::interrupt()
或boost::condition_variable::notify_*()
不再。
Solution
解决方案
Removing the line mlock.unlock()
made the code work as expected (notifications and interrupts end the wait). Note that notify_one()
is called with the mutex still locked, it is unlocked right afterwards when leaving the scope:
删除该行使mlock.unlock()
代码按预期工作(通知和中断结束等待)。请注意,notify_one()
在互斥锁仍然锁定的情况下调用,它在离开作用域时立即解锁:
void push(const T& item)
{
std::lock_guard<std::mutex> mlock(mutex_);
queue_.push(item);
cond_.notify_one(); // notify one waiting thread
}
That means that at least with my particular thread implementation the mutex must not be unlocked before calling boost::condition_variable::notify_one()
, although both ways seem correct.
这意味着至少在我的特定线程实现中,互斥锁在调用之前不能被解锁boost::condition_variable::notify_one()
,尽管两种方式看起来都是正确的。
回答by cantunca
As others have pointed out, you do not need to be holding the lock when calling notify_one()
, in terms of race conditions and threading-related issues. However, in some cases, holding the lock may be required to prevent the condition_variable
from getting destroyed before notify_one()
is called. Consider the following example:
正如其他人指出的那样,notify_one()
就竞争条件和线程相关的问题而言,在调用 时不需要持有锁。但是,在某些情况下,可能需要持有锁以防止在调用condition_variable
之前notify_one()
被破坏。考虑以下示例:
thread t;
void foo() {
std::mutex m;
std::condition_variable cv;
bool done = false;
t = std::thread([&]() {
{
std::lock_guard<std::mutex> l(m); // (1)
done = true; // (2)
} // (3)
cv.notify_one(); // (4)
}); // (5)
std::unique_lock<std::mutex> lock(m); // (6)
cv.wait(lock, [&done]() { return done; }); // (7)
}
void main() {
foo(); // (8)
t.join(); // (9)
}
Assume there is a context switch to the newly created thread t
after we created it but before we start waiting on the condition variable (somewhere between (5) and (6)). The thread t
acquires the lock (1), sets the predicate variable (2) and then releases the lock (3). Assume there is another context switch right at this point before notify_one()
(4) is executed. The main thread acquires the lock (6) and executes line (7), at which point the predicate returns true
and there is no reason to wait, so it releases the lock and continues. foo
returns (8) and the variables in its scope (including cv
) are destroyed. Before thread t
could join the main thread (9), it has to finish its execution, so it continues from where it left off to execute cv.notify_one()
(4), at which point cv
is already destroyed!
假设t
在我们创建新创建的线程之后但在我们开始等待条件变量之前(在 (5) 和 (6) 之间的某个位置)有一个上下文切换到新创建的线程。线程t
获取锁 (1),设置谓词变量 (2),然后释放锁 (3)。假设在notify_one()
执行 (4)之前此时还有另一个上下文切换。主线程获取锁(6)并执行第(7)行,此时谓词返回true
并且没有理由等待,因此它释放锁并继续。foo
返回 (8) 并且其范围内的变量(包括cv
)被销毁。在线程t
可以加入主线程(9)之前,它必须完成它的执行,所以它从它停下来的地方继续执行cv.notify_one()
(4),此时cv
就已经销毁了!
The possible fix in this case is to keep holding the lock when calling notify_one
(i.e. remove the scope ending in line (3)). By doing so, we ensure that thread t
calls notify_one
before cv.wait
can check the newly set predicate variable and continue, since it would need to acquire the lock, which t
?is currently holding, to do the check. So, we ensure that cv
is not accessed by thread t
after foo
returns.
在这种情况下可能的解决方法是在调用时保持锁定notify_one
(即删除以第 (3) 行结尾的范围)。通过这样做,我们确保之前的线程t
调用可以检查新设置的谓词变量并继续,因为它需要获取当前持有的锁来进行检查。所以,我们保证返回后不被线程访问。notify_one
cv.wait
t
cv
t
foo
To summarize, the problem in this specific case is not really about threading, but about the lifetimes of the variables captured by reference. cv
is captured by reference via thread t
, hence you have to make sure cv
stays alive for the duration of the thread's execution. The other examples presented here do not suffer from this issue, because condition_variable
and mutex
objects are defined in the global scope, hence they are guaranteed to be kept alive until the program exits.
总而言之,这种特定情况下的问题实际上并不与线程有关,而是与通过引用捕获的变量的生命周期有关。cv
通过 thread 通过引用捕获t
,因此您必须确保cv
在线程执行期间保持活动状态。此处介绍的其他示例不会遇到此问题,因为condition_variable
和mutex
对象是在全局范围内定义的,因此可以保证它们在程序退出之前一直保持活动状态。
回答by Fan Jing
In some case, when the cv may be occupied(locked) by other threads. You needs to get lock and release it before notify_*().
If not, the notify_*() maybe not executed at all.
在某些情况下,当 cv 可能被其他线程占用(锁定)时。您需要在notify_*() 之前获得锁定并释放它。
如果没有,则可能根本不会执行 notify_*() 。
回答by Carlo Wood
Just adding this answer because I think the accepted answer might be misleading. In all cases you will need to lock the mutex, prior to calling notify_one() somewherefor your code to be thread-safe, although you might unlock it again before actually calling notify_*().
只是添加这个答案,因为我认为接受的答案可能会产生误导。在所有情况下,您都需要在某处调用 notify_one() 之前锁定互斥锁,以使您的代码成为线程安全的,尽管您可能会在实际调用 notify_*() 之前再次解锁它。
To clarify, you MUST take the lock before entering wait(lk) because wait() unlocks lk and it would be Undefined Behavior if the lock wasn't locked. This is not the case with notify_one(), but you need to make sure you won't call notify_*() before entering wait() andhaving that call unlock the mutex; which obviously only can be done by locking that same mutex before you call notify_*().
澄清一下,您必须在进入 wait(lk) 之前获取锁定,因为 wait() 解锁 lk,如果锁定未锁定,它将是未定义行为。notify_one() 不是这种情况,但是您需要确保在进入 wait() 之前不会调用 notify_*()并让该调用解锁互斥锁;这显然只能通过在调用 notify_*() 之前锁定相同的互斥锁来完成。
For example, consider the following case:
例如,考虑以下情况:
std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;
void stop()
{
if (count.fetch_sub(1) == -999) // Reached -1000 ?
cv.notify_one();
}
bool start()
{
if (count.fetch_add(1) >= 0)
return true;
// Failure.
stop();
return false;
}
void cancel()
{
if (count.fetch_sub(1000) == 0) // Reached -1000?
return;
// Wait till count reached -1000.
std::unique_lock<std::mutex> lk(cancel_mutex);
cancel_cv.wait(lk);
}
Warning: this code contains a bug.
警告:此代码包含一个错误。
The idea is the following: threads call start() and stop() in pairs, but only as long as start() returned true. For example:
想法如下:线程成对调用 start() 和 stop(),但前提是 start() 返回 true。例如:
if (start())
{
// Do stuff
stop();
}
One (other) thread at some point will call cancel() and after returning from cancel() will destroy objects that are needed at 'Do stuff'. However, cancel() is supposed not to return while there are threads between start() and stop(), and once cancel() executed its first line, start() will always return false, so no new threads will enter the 'Do stuff' area.
一个(另一个)线程在某个时候会调用 cancel() 并且在从 cancel() 返回后将销毁“做事”所需的对象。但是,当 start() 和 stop() 之间存在线程时,cancel() 不应该返回,并且一旦 cancel() 执行其第一行,start() 将始终返回 false,因此没有新线程将进入“Do东西区。
Works right?
行得通吗?
The reasoning is as follows:
理由如下:
1) If any thread successfully executes the first line of start() (and therefore will return true) then no thread did execute the first line of cancel() yet (we assume that the total number of threads is much smaller than 1000 by the way).
1) 如果任何线程成功执行了 start() 的第一行(因此将返回 true),那么还没有线程确实执行了 cancel() 的第一行(我们假设线程总数远小于 1000道路)。
2) Also, while a thread successfully executed the first line of start(), but not yet the first line of stop() then it is impossible that any thread will successfully execute the first line of cancel() (note that only one thread ever calls cancel()): the value returned by fetch_sub(1000) will be larger than 0.
2) 另外,虽然一个线程成功执行了 start() 的第一行,但还没有执行完 stop() 的第一行,那么任何线程都不可能成功执行 cancel() 的第一行(注意只有一个线程曾经调用 cancel()):fetch_sub(1000) 返回的值将大于 0。
3) Once a thread executed the first line of cancel(), the first line of start() will always return false and a thread calling start() will not enter the 'Do stuff' area anymore.
3) 一旦线程执行了cancel() 的第一行,start() 的第一行将始终返回false,并且调用start() 的线程将不再进入'Do stuff' 区域。
4) The number of calls to start() and stop() are always balanced, so after the first line of cancel() is unsuccessfully executed, there will always be a moment where a (the last) call to stop() causes count to reach -1000 and therefore notify_one() to be called. Note that can only ever happen when the first line of cancel resulted in that thread to fall through.
4)start()和stop()的调用次数总是平衡的,所以在第一行cancel()执行失败后,总会有(最后一次)调用stop()导致count的时刻达到 -1000 并因此调用 notify_one() 。请注意,只有在第一行取消导致该线程失败时才会发生。
Apart from a starvation problem where so many threads are calling start()/stop() that count never reaches -1000 and cancel() never returns, which one might accept as "unlikely and never lasting long", there is another bug:
除了如此多的线程调用 start()/stop() 并且计数永远不会达到 -1000 并且 cancel() 永远不会返回的饥饿问题之外,人们可能会接受“不太可能并且永远不会持续很长时间”,还有另一个错误:
It is possible that there is one thread inside the 'Do stuff' area, lets say it is just calling stop(); at that moment a thread executes the first line of cancel() reading the value 1 with the fetch_sub(1000) and falling through. But before it takes the mutex and/or does the call to wait(lk), the first thread executes the first line of stop(), reads -999 and calls cv.notify_one()!
'Do stuff' 区域内可能有一个线程,可以说它只是在调用 stop(); 在那一刻,一个线程执行第一行 cancel() 读取值 1 与 fetch_sub(1000) 并落下。但在获取互斥锁和/或调用 wait(lk) 之前,第一个线程执行 stop() 的第一行,读取 -999 并调用 cv.notify_one()!
Then this call to notify_one() is done BEFORE we are wait()-ing on the condition variable! And the program would indefinitely dead-lock.
然后对 notify_one() 的调用在我们对条件变量进行 wait() 之前完成!并且程序会无限期地死锁。
For this reason we should not be able to call notify_one() untilwe called wait(). Note that the power of a condition variable lies there in that it is able to atomically unlock the mutex, check if a call to notify_one() happened and go to sleep or not. You can't fool it, but you doneed to keep the mutex locked whenever you make changes to variables that might change the condition from false to true and keepit locked while calling notify_one() because of race conditions like described here.
因此,在调用wait()之前,我们不应该调用notify_one ()。请注意,条件变量的强大之处在于它能够自动解锁互斥锁,检查是否调用了 notify_one() 并进入睡眠状态。您不能愚弄它,但是当您更改可能将条件从 false 更改为 true 的变量时,您确实需要保持互斥锁锁定,并在调用 notify_one() 时保持锁定,因为这里描述的竞争条件。
In this example there is no condition however. Why didn't I use as condition 'count == -1000'? Because that isn't interesting at all here: as soon as -1000 is reached at all, we are sure that no new thread will enter the 'Do stuff' area. Moreover, threads can still call start() and will increment count (to -999 and -998 etc) but we don't care about that. The only thing that matters is that -1000 was reached - so that we know for sure that there are no threads anymore in the 'Do stuff' area. We are sure that this is the case when notify_one() is being called, but how to make sure we don't call notify_one() before cancel() locked its mutex? Just locking cancel_mutex shortly prior to notify_one() isn't going to help of course.
但是在这个例子中没有条件。为什么我不使用条件 'count == -1000'?因为这在这里一点都不有趣:一旦达到 -1000,我们确信没有新线程将进入“执行任务”区域。此外,线程仍然可以调用 start() 并将增加计数(到 -999 和 -998 等),但我们不在乎。唯一重要的是达到了 -1000 - 这样我们就可以肯定地知道“做东西”区域中不再有线程了。我们确定在调用 notify_one() 时就是这种情况,但是如何确保在 cancel() 锁定其互斥锁之前我们不调用 notify_one() 呢?在notify_one() 之前不久锁定cancel_mutex 当然不会有帮助。
The problem is that, despite that we're not waiting for a condition, there still isa condition, and we need to lock the mutex
问题是,尽管我们不是等待状态,还有就是一个条件,我们需要锁定互斥
1) before that condition is reached 2) before we call notify_one.
1) 在达到该条件之前 2) 在我们调用 notify_one 之前。
The correct code therefore becomes:
因此,正确的代码变为:
void stop()
{
if (count.fetch_sub(1) == -999) // Reached -1000 ?
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[...same start()...]
[...同样的开始()...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
Of course this is just one example but other cases are very much alike; in almost all cases where you use a conditional variable you will needto have that mutex locked (shortly) before calling notify_one(), or else it is possible that you call it before calling wait().
当然这只是一个例子,但其他情况非常相似;在几乎所有使用条件变量的情况下,您都需要在调用 notify_one() 之前(不久)锁定该互斥锁,否则可能在调用 wait() 之前调用它。
Note that I unlocked the mutex prior to calling notify_one() in this case, because otherwise there is the (small) chance that the call to notify_one() wakes up the thread waiting for the condition variable which then will try to take the mutex and block, before we release the mutex again. That's just slightly slower than needed.
请注意,在这种情况下,我在调用 notify_one() 之前解锁了互斥锁,因为否则有(小)机会调用 notify_one() 唤醒等待条件变量的线程,然后该线程将尝试获取互斥锁和在我们再次释放互斥锁之前阻止。这只是比需要的慢一点。
This example was kinda special in that the line that changes the condition is executed by the same thread that calls wait().
这个例子有点特别,因为改变条件的行是由调用 wait() 的同一个线程执行的。
More usual is the case where one thread simply wait's for a condition to become true and another thread takes the lock before changing the variables involved in that condition (causing it to possibly become true). In that case the mutex islocked immediately before (and after) the condition became true - so it is totally ok to just unlock the mutex before calling notify_*() in that case.
更常见的情况是一个线程简单地等待条件变为真,而另一个线程在更改该条件中涉及的变量之前获取锁(导致它可能变为真)。在这种情况下,互斥锁在条件变为真之前(和之后)立即被锁定 - 所以在这种情况下,在调用 notify_*() 之前解锁互斥锁是完全可以的。
回答by didierc
@Michael Burr is correct. condition_variable::notify_one
does not require a lock on the variable. Nothing prevents you to use a lock in that situation though, as the example illustrates it.
@Michael Burr 是正确的。condition_variable::notify_one
不需要锁定变量。但是,正如示例所示,没有什么可以阻止您在这种情况下使用锁。
In the given example, the lock is motivated by the concurrent use of the variable i
. Because the signals
thread modifiesthe variable, it needs to ensure that no other thread is access it during that time.
在给定的示例中,锁的动机是并发使用变量i
。因为signals
线程修改了变量,它需要确保在这段时间内没有其他线程访问它。
Locks are used for any situation requiring synchronization, I don't think we can state it in a more general way.
锁用于任何需要同步的情况,我认为我们不能用更一般的方式来说明它。