在condition_variable :: notify_all()之后或之前解锁互斥锁?

时间:2018-09-25 17:01:35

标签: c++ multithreading condition-variable

several videosdocumentation example,我们在调用notify_all()之前解锁互斥锁 。改个以后叫它会更好吗?

常用方法:

在通告程序线程内:

//prepare data for several worker-threads;

//and now, awaken the threads:
std::unique_lock<std::mutex> lock2(sharedMutex);
_threadsCanAwaken = true;

lock2.unlock(); 
_conditionVar.notify_all(); //awaken all the worker threads;

//wait until all threads completed;

//cleanup:
_threadsCanAwaken = false;

//prepare new batches once again, etc, etc

在其中一个工作线程中:

while(true){
    // wait for the next batch:

    std::unique_lock<std::mutex> lock1(sharedMutex);
    _conditionVar.wait(lock1,  [](){return _threadsCanAwaken});
    lock1.unlock(); //let sibling worker-threads work on their part as well

    //perform the final task

    //signal the notifier that one more thread has completed;

    //loop back and wait until the next task
}

在通知条件变量之前通知lock2是如何解锁的-我们应该在notify_all()之后 之后解锁它吗?

4 个答案:

答案 0 :(得分:2)

除非您的实现不寻常,否则在向条件变量发送信号之前解锁互斥锁没有任何好处。在发信号之前解锁有两个缺点:

  1. 如果在发出信号之前解锁,信号可能会唤醒一个线程,该线程在解锁后选择阻塞条件变量。如果您使用相同的条件变量来发出多个逻辑条件的信号,这可能会导致死锁。这种错误很难创建,很难诊断,也很难理解。通过在解锁之前始终发出信号可以轻松避免。这确保了共享状态和信号的更改是原子操作,并且不会出现竞争条件和死锁。

  2. 在发信号前解锁会导致性能损失,而在发信号后解锁会避免这种情况。如果您在解锁之前发出信号,一个好的实现将知道您的信号不可能呈现任何准备运行的线程,因为互斥锁由调用线程持有,并且任何受条件变量影响的线程在没有互斥锁的情况下必然无法前进.这允许进行重大优化(通常称为“等待变形”),如果您先解锁,这是不可能的。

所以在持有锁时发出信号,除非你有一些不寻常的理由不这样做。

答案 1 :(得分:1)

如此处所述:cppreference.com

  

通知线程不需要将锁保持在同一互斥锁上   作为等待线程持有的线程;实际上,这样做是   悲观,因为通知的线程将立即阻塞   再次,等待通知线程释放锁。

话说,documentation for wait

  

在阻塞线程时,该函数自动调用   lck.unlock(),允许其他锁定的线程继续。

     

一旦(通过其他线程明确地)通知了该函数   解除阻止并调用lck.lock(),使lck处于与when相同的状态   该函数被调用。然后函数返回(注意   最后的互斥锁可能会在返回之前再次阻塞线程。)

因此,当收到通知时,等待将重新尝试获得该锁,在此过程中,它将再次被阻塞,直到原始通知线程释放该锁为止。 因此,我建议在调用notify之前释放锁。如在cppreference.com上的示例中所做的,最重要的是

  

不要悲观。

答案 2 :(得分:0)

  

我们应该改为在notify_all()之后将其解锁吗?

这两种方法都是正确的,但是在不同情况下您可能会有不同的行为。很难预测它将如何影响程序性能-我已经看到了不同应用程序的正面和负面影响。因此,最好对程序进行概要分析,并根据性能分析来确定特定情况。

答案 3 :(得分:0)

<块引用>

我担心的是,如果消费者超级快怎么办。消费者 虚假唤醒,看到互斥锁被解锁,完成任务 并循环回到 while 的开头。现在慢戳制作人 最后调用notify_all(),导致消费者循环一个额外的 时间。

来自cpp reference

<块引用>

当条件变量被通知时,超时到期,或 出现虚假唤醒,线程被唤醒,互斥量被唤醒 原子重新获取。然后线程应该检查条件并 如果唤醒是虚假的,则继续等待。

    private ArrayList subscribers;
    public Form2(Form1 form)

    {
        InitializeComponent();
        form.arrSubscribers = MyMethod;
    }

    public void MyMethod(ArrayList list)
    {
        subscribers = list;
    }

    private void Click(object sender, EventArgs e)
    {
        var query = from string item in subscribers
                    select item;
        label1.Text = string.Join(", ", query);
    }