当前位置: 首页 > 知识库问答 >
问题:

在调用条件_变量之前是否必须获取锁。通知某人?

俞博涛
2023-03-14

我对std::condition_变量的用法有点困惑。我知道在调用条件变量之前,我必须在互斥锁上创建一个唯一锁。等待()。我找不到的是,在调用notify_one()notify_all()之前,我是否还应该获取一个唯一的锁。

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

在这里,锁不是为第一个notify_one()获取的,而是为第二个notify_one()获取的。查看其他页面上的示例,我看到了不同的东西,大多数情况下没有获得锁。

  • 在调用notify_one()之前,我可以选择自己锁定互斥锁吗?我为什么选择锁定它

共有3个答案

徐文彬
2023-03-14
匿名用户

使用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
}

解锁互斥锁有Boost文档中的一个示例支持:

void prepare_data_for_processing()
{
    retrieve_data();
    prepare_data();
    {
        boost::lock_guard<boost::mutex> lock(mut);
        data_ready=true;
    }
    cond.notify_one();
}

然而,这导致了以下不稳定的行为:

  • notify_one()尚未调用。wait()仍然可以通过boost::thread::interrupt()
  • 一旦第一次调用了notify_one()。wait()死锁;等待不能再通过boost::thread::interrupt()boost::condition_variable::notify_*()来结束

移除行mlock。解锁()使代码按预期工作(通知和中断结束等待)。请注意,notify_one()

void push(const T& item)
{
  std::lock_guard<std::mutex> mlock(mutex_);
  queue_.push(item);
  cond_.notify_one(); // notify one waiting thread
}

这意味着,至少在我的特定线程实现中,在调用boost::condition_variable::notify_one()之前,不能解锁互斥锁,尽管这两种方法似乎都是正确的。

杭泉
2023-03-14

正如其他人所指出的,在调用notify_one()时,您不需要在种族条件和线程相关问题方面持有锁。但是,在某些情况下,在调用notify_one()之前,可能需要保持锁以防止condition_变量被破坏。考虑以下示例:

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

假设在我们创建新创建的线程t之后但在我们开始等待条件变量之前(介于(5)和(6)之间)有一个上下文切换。线程t获取锁(1),设置谓词变量(2),然后释放锁(3)。假设在执行notify_one()(4)之前此时还有另一个上下文切换。主线程获取锁(6)并执行第(7)行,此时谓词返回true并且没有等待的理由,因此它释放锁并继续。foo返回(8)并且其范围内的变量(包括cv)被销毁。在线程t可以加入主线程(9)之前,它必须完成它的执行,因此它从中断的地方继续执行cv.notify_one()(4),此时cv已经被销毁!

在这种情况下,可能的修复方法是在调用notify_one时保持锁定(即移除以第(3)行结尾的作用域)。通过这样做,我们确保线程tcv之前调用notify_one。wait可以检查新设置的谓词变量并继续,因为它需要获取锁,而锁是t

总而言之,这个特定案例中的问题实际上与线程无关,而是与引用捕获的变量的生命周期有关cv通过线程t通过引用捕获,因此必须确保cv在线程执行期间保持活动状态。这里给出的其他示例没有遇到这个问题,因为条件_变量互斥对象是在全局范围内定义的,因此它们保证在程序退出之前保持活动状态。

应煌
2023-03-14

在调用条件变量::notify_one()时,不需要持有锁,但它仍然是定义良好的行为,而不是错误,这并不是错误。

然而,这可能是一种“悲观情绪”,因为任何使等待线程可运行的线程(如果有的话)都会立即尝试获取通知线程持有的锁。我认为在调用notify_one()notify_all()时,避免持有与条件变量相关联的锁是一个很好的经验法则。请参阅Pthread Mutex:Pthread_Mutex_unlock()消耗大量时间,例如,在调用Pthread等价于notify_one()的函数之前释放锁,可以显著提高性能。

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

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

关键是i发生了什么-这是实际指示消费者是否有“工作”要做的对象。condition_变量只是一种机制,让使用者高效地等待对i的更改。

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

C标准表示,使用谓词调用condition_variable::wait()时,其行为如下(如本例所示):

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

当消费者检查i时,可能会出现两种情况:

>

如果消费者呼叫cv时i已为1。wait(),实现的等待(锁定)部分将永远不会被调用,因为while(!pred())测试将导致内部循环终止。在这种情况下,无论何时调用notify_one(),消费者都不会阻止。

这里的示例确实具有额外的复杂性,即使用done变量向生产者线程发回信号,表明消费者已经识别出i==1,但我认为这根本不会改变分析,因为所有对done(用于读取和修改)的访问都是在涉及I条件变量的相同关键部分中完成的。

如果你看一下@eh9指出的问题,使用std::atomic和std::condition_变量同步是不可靠的,你会看到一个竞争条件。然而,该问题中发布的代码违反了使用条件变量的基本规则之一:在执行检查和等待时,它不包含任何关键部分。

在该示例中,代码如下所示:

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处的etc()是在持有f-时执行

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

 类似资料:
  • 问题内容: 我在服务器端有PHP,在客户端有HTML和javascript。 我正在制作一个应用程序,其中利益相关者键入一条消息,该消息实时广播到一个组的多个接收者。 我在Google上做了一些研究,我了解我需要使用WebSockets或Comet进行实时推送通知。WebSocket或Comet是向用户发送大量通知的必需项吗? 我的理解正确吗?有什么参考开始吗? 问题答案: 如果客户端是浏览器,则

  • 问题内容: 我收到此错误。如下面的代码所示,该;行位于代码行之前;。在我的每一项活动中,此onCreate()代码都是相同的格式,到目前为止,我从没有遇到过麻烦。自从我将ADT更新为22以来,到处都出现了许多随机错误。我已经克服了许多错误,这是我的最新错误。 我该如何解决此错误? 问题答案: 我也遇到了这个问题,但是当我在调用super.onCreate()之前调用窗口请求时,问题就解决了,请尝试

  • 我有以下代码: 假设线程1调用synchornizer.await()并通过 和块在 然后,另一个线程2调用synchronizer.signalAll()以向线程1发送信号。我的问题是线程2如何通过调用 打电话之前 锁最初是由线程 1 获取的? 我在这里发现了同样的问题: 等待可重入锁中的条件 答案是: “锁定”和“已同步”都暂时允许其他人在等待时获取锁定。要停止等待,线程必须重新获取锁。 我试

  • Python有一个名为<code>Condition或<code>notify_all()。然而,在调用<code>wait()acquire()wait()方法然后释放锁并等待通知,然后它将继续重新获取锁,您可以运行一些需要线程安全的代码。我的问题是,当调用<code>wait()方法时,<code>条件</code>对象为什么不在内部自动获取锁: 必须调用其他方法并按住关联的锁。 方法释放锁,

  • 从理论上讲,一个等待线程(假设Thread_1)首先获取一个互斥体,然后通过调用wait()等待条件变量。对wait()的调用会立即解锁互斥体。当另一个线程(比如Thread_2)调用notify()时,等待的线程(Thread_1)被唤醒,相同的互斥体在等待之前被锁定(..)调用返回。 现在假设多个线程在给定的时间等待一个条件变量(假设是Thread_1、Thread_2和Thread_3)。现

  • 问题内容: 为什么不起作用? 无法理解为什么作业的左侧不是变量。 有人帮忙吗? 问题答案: 如果您只想增加5,而又不限于此,则可以避免冗长,而可以这样做: 这将in- in 的值原地增加5。