在调用condition_variable.notify_one() 之前是否必须获取锁? [英] Do I have to acquire lock before calling condition_variable.notify_one()?

查看:65
本文介绍了在调用condition_variable.notify_one() 之前是否必须获取锁?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 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(),则iwait(lock) 部分实现被调用 - 正确使用锁确保了这一点.在这种情况下,生产者没有机会在其 while 循环中调用 condition_variable::notify_one() 直到消费者调用 cv.wait(lk,[]{return i == 1;}) (并且 wait() 调用已经完成了正确捕捉"通知所需的一切 - wait() 在它完成之前不会释放锁).所以在这种情况下,消费者不会错过通知.

  • if i is 0 then the consumer calls cv.wait(), then i will still be 0 when the wait(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 the condition_variable::notify_one() in its while loop until after the consumer has called cv.wait(lk, []{return i == 1;}) (and the wait() 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 的所有访问(用于读取和修改)都是在涉及 icondition_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() 之前是否必须获取锁?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆