gpt4 book ai didi

c++ - 在调用condition_variable.notify_one() 之前是否必须获取锁?

转载 作者:IT老高 更新时间:2023-10-28 12:10:17 34 4
gpt4 key购买 nike

我对std::condition_variable的使用有点困惑.我知道我必须创建一个 unique_lockmutex调用前 condition_variable.wait() .我找不到的是我是否也应该在调用 notify_one() 之前获取唯一锁或 notify_all() .

关于 cppreference.com 的示例是矛盾的。例如,notify_one page给出这个例子:

#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();
}

这里没有为第一个 notify_one() 获取锁,但为第二个 notify_one() 获得.通过示例查看其他页面,我看到了不同的东西,主要是没有获取锁。
  • 我可以在调用之前选择自己锁定互斥锁notify_one() ,为什么我会选择锁定它?
  • 在给出的示例中,为什么第一个 notify_one() 没有锁,但有用于后续调用。这个例子是错误的还是有一些理由?
  • 最佳答案

    拨打 condition_variable::notify_one() 时不需要持有锁,但从某种意义上说它仍然是明确定义的行为而不是错误,这并没有错。

    然而,这可能是一种“悲观化”,因为任何等待线程变为可运行的(如果有)都会立即尝试获取通知线程持有的锁。我认为在调用 notify_one() 时避免持有与条件变量关联的锁是一个很好的经验法则。或 notify_all() .见 Pthread Mutex: pthread_mutex_unlock() consumes lots of time例如,在调用与 notify_one() 等效的 pthread 之前释放锁显着提高了性能。

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

    2016-02-27 :解决评论中关于是否存在竞争条件的一些问题的大更新,锁定对 notify_one() 没有帮助打电话。我知道这个更新晚了,因为这个问题是在两年前提出的,但我想解决@Cookie 关于可能的竞争条件的问题,如果生产者(在本例中为 signals())调用 notify_one()就在消费者(在本例中为 waits())能够调用 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 wait(lock) 时仍为 0实现的一部分被称为 - 正确使用锁确保了这一点。在这种情况下,生产者没有机会调用 condition_variable::notify_one()在其 while循环直到消费者调用 cv.wait(lk, []{return i == 1;}) (并且 wait() 调用已经完成了正确“捕获”通知所需的一切 - wait() 在完成之前不会释放锁)。所以在这种情况下,消费者不能错过通知。
  • 如果 i当消费者调用 cv.wait() 时已经是 1 , wait(lock)部分实现永远不会被调用,因为 while (!pred())测试将导致内部循环终止。在这种情况下,何时调用 notify_one() 无关紧要 - 消费者不会阻塞。

  • 此处的示例确实具有使用 done 的额外复杂性。变量以向生产者线程发回信号,表明消费者已认识到 i == 1 ,但我认为这根本不会改变分析,因为所有访问 done (用于阅读和修改)是在涉及 i 的相同临界区中完成的。和 condition_variable .

    如果你看看@eh9 指出的问题, Sync is unreliable using std::atomic and std::condition_variable ,您将看到竞争条件。但是,该问题中发布的代码违反了使用条件变量的基本规则之一:在执行检查和等待时,它不包含单个临界区。

    在该示例中,代码如下所示:
    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)
    }

    您会注意到 wait()在#3 处执行同时持有 f->resume_mutex .但检查是否 wait()在第 1 步是必要的,而根本没有持有该锁(对于检查和等待而言,连续性要少得多),这是正确使用条件变量的要求)。我相信那个代码片段有问题的人认为,自从 f->counterstd::atomic键入这将满足要求。但是, std::atomic 提供的原子性不会扩展到对 f->resume.wait(lock) 的后续调用.在这个例子中,在 f->counter 之间存在竞争。检查(步骤#1)并且当 wait()被调用(步骤#3)。

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

    关于c++ - 在调用condition_variable.notify_one() 之前是否必须获取锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17101922/

    34 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com