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

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

问题描述

我对使用 std :: condition_variable 有点困惑。我理解我必须在调用 condition_variable.wait()之前在 mutex 上创建 unique_lock 。我找不到是否在调用 notify_one() notify_all()之前还应该获取一个唯一的锁。 / p>

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 ... \\\
;
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<< 通知... \\\
;
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<< 再次通知... \\\
;
cv.notify_one();
}
}

int main()
{
std :: thread t1(waits),t2
t1.join(); t2.join();
}

这里不会获取第一个 notify_one (),但是获取第二个 notify_one()




  • 我可以选择自己锁定互斥,然后再调用 notify_one(),为什么我会选择锁定它?

  • 在给出的示例中, code> notify_one(),但有后续调用。这个例子是错误的还是有一些理由?


解决方案

当调用 condition_variable :: notify_one()时,锁定,但它没有错,因为它仍然是良好定义的行为,而不是错误。



然而,它可能是一个pessimization,因为无论等待线程是否可运行(如果有)将立即尝试获取通知线程持有的锁。我认为这是一个好的经验法则,在调用 notify_one() notify_all()。请参见 Pthread Mutex:pthread_mutex_unlock()消耗大量时间



保存在

记住在 while 循环中的 lock()调用在某些时候是必要的,因为锁需要在 while(!done)循环条件检查期间保持。但它不需要为 notify_one()的调用。






2016-02-27 :大量更新以解决有关是否存在争用条件的注释中的一些问题是锁对 notify_one没有帮助()调用。我知道这个更新是晚了,因为问题是在两年前提出的,但我想解决@ Cookie关于一个可能的竞争条件的问题如果生产者( signals()在此示例中)在消费者(在本示例中 waits()之前)调用 notify_one()调用 wait()



关键是 i - 这是实际指示消费者是否工作做的对象。 condition_variable 只是一种机制,让消费者有效地等待对 i 的更改。



当更新 i 时,生产者需要保持锁,消费者必须持有锁,同时检查 i 并调用 condition_variable :: wait()(如果它需要等待)。在这种情况下,关键是当消费者进行这种检查和等待时,它必须是持有锁的实例(通常称为临界区)。因为当生产者更新 i 时,并且当消费者检查并等待 i 时,没有机会使 i 在消费者检查 i 和调用 condition_variable :: wait()。这是正确使用条件变量的关键。



C ++标准说,condition_variable :: wait()在使用谓词(如本例)调用时,表现如下:

  while(!pred())
wait(lock);

当消费者检查 i




  • 如果 i 为0,调用 cv.wait(),则 i 部分实现被调用 - 正确使用锁确保。在这种情况下,生产者没有机会在其中调用 condition_variable :: notify_one(),而消费者已经调用 cv.wait(lk,[] {return i == 1;})(和 wait()调用已经做了它所需要做的一切,以正确地捕获通知 - wait()将不释放锁,直到它这样做)。


  • 如果 i 已经是1,消费者调用 cv.wait(),实现的 wait(lock)部分将永远不会被调用, code> while(!pred())测试将导致内部循环终止。在这种情况下,当notify_one()调用发生时无关紧要 - 消费者不会阻塞。




这里的例子确实具有使用 done 变量来向生产者线程发信号通知消费者已经认识到 i == 1 ,但我不认为这改变了分析,因为所有访问 done (读取和修改) i condition_variable



如果您查看@ eh9指向的问题,同步是不可靠使用std :: atomic和std :: condition_variable ,您将看到一个竞争条件。但是,在该问题中发布的代码违反了使用条件变量的基本规则之一:在执行检查和等待时,它不包含单个关键段。



<在这个例子中,代码如下:

  if(--f-> counter == 0)// (1)
//我们把这个围栏的计数器归零,唤醒每个等待
的人f-> resume.notify_all(); //(2)
else
{
unique_lock< mutex> lock(f-> resume_mutex);
f-> resume.wait(lock); //(3)
}

你会注意到保持 f-> resume_mutex 时执行#3处的wait()。但是在步骤#1检查是否需要 wait(),而不是一直保持该锁(更不连续地)用于检查和等待),这是正确使用条件变量的要求)。我相信那个代码片段的问题的人认为,因为 f-> counter 是一个 std :: atomic type这将满足要求。但是,由 std :: atomic 提供的原子性不会扩展到 f-> resume.wait(lock)。在这个例子中,当检查 f->计数器(步骤#1)和 wait() $ c
$ b

这个问题的例子中不存在这个种族。


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().

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... \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.

  • 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?

解决方案

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.

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.

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: 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().

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.

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.

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);

There are two situations that can occur when the consumer checks i:

  • 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.

  • 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.

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.

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)
}

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天全站免登陆