【问题标题】:Order how multiple threads reacquire std::unique_lock<std::mutex> after std::condition_variable::notify_all排序多个线程如何在 std::condition_variable::notify_all 之后重新获取 std::unique_lock<std::mutex>
【发布时间】:2017-04-22 17:45:28
【问题描述】:

这是一段将从多个线程调用的代码。 std::condition_variable::wait 的谓词对于每个线程都会略有不同,但不会改变问题。

std::unique_lock<std::mutex> lock{connection_mutex};
cv.wait(lock, 
     [conn = shared_from_this()]
     {
         return conn->connection_is_made();
     }
);

//do some stuff
lock.unlock();

我在cppreference读到这篇文章

当通知条件变量、超时过期或发生虚假唤醒时,线程被唤醒,互斥量被原子地重新获取。如果唤醒是虚假的,线程应该检查条件并继续等待。

据我所知,lock 将被锁定,然后会检查谓词 lambda,如果它返回 truelock 将保持锁定状态,我可以在此保护下继续做一些事情互斥体。当std::condition_variablenotify_one() 成员函数通知时,这是非常有意义的。

但是当std::condition_variablenotify_all() 通知时会发生什么?如果所有线程都被唤醒并且比“排队等待”来锁定互斥锁,然后才检查谓词返回什么,或者他们可能会做其他事情,我无法在文档中找到。


编辑:在看到下面的 cmets 后,我开始思考。 std::condition_variable::wait 期望 std::unique_lock 作为它的第一个参数,当 std::condition_variable::wait 在通知后被唤醒时 - std::unique_lock 将被重新获取。现在,如果多个线程正在等待同一个通知,那么当最终在特定的 std::condition_variable 上调用 notify_all() 时,只有 1 个线程能够锁定互斥锁,所有其他线程将重新进入睡眠状态。因此,如果 notify_all() 成员函数与 notify_one() 具有相同的效果,但效率较低,我根本看不到它有任何意义。我的意思是,如果必须重新获取互斥锁 一个线程通过std::condition_variable::wait,那么所有等待的线程不可能同时完成。

【问题讨论】:

  • 如果您询问通知顺序,那么这可能会有所帮助:stackoverflow.com/questions/15912322/…
  • 感谢您的回答,但在这种情况下,我并不担心永远不会收到 notify_all() 信号。我只想知道多个线程在等待同一个condition_variable时是怎么做的,然后通过notify_all()接收通知,然后尝试获取同一个mutex。
  • 这取决于操作系统调度程序。您无法知道哪个线程首先获得互斥锁。
  • 他们在排队吗? 1 个线程得到它,做它的事情,当互斥锁被释放时,另一个线程立即重新获取互斥锁并检查它的谓词等等,直到所有等待的线程重新获得互斥锁?
  • 根据cppreference,所有线程都被唤醒。但是,如果他们只是要为互斥锁“排队”,那么除了一个之外,其他所有人都会回去睡觉。那么在那种情况下,为什么要调用全部通知而不是通知一个呢?如果所有等待的线程可以同时取得进展,Notify all 很好。

标签: c++ multithreading c++11


【解决方案1】:

我做了一些测试,看看是否所有线程都在调用 notify_all() 后被唤醒,即使是那些没有计划作为第一个被唤醒的线程。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::mutex M;
std::condition_variable CV;
int var = 0;


int main()
{
    std::thread t1{
        []{
            std::unique_lock<std::mutex> lck{M};
            while(var != 1){
                CV.wait(lck);
                std::cout << "t1 woken up and (i != 1) = " 
                          << (var != 1) << std::endl;
            }
        }
    };

    std::thread t2{
        []{
            std::unique_lock<std::mutex> lck{M};
            while(var != 2){
                CV.wait(lck);
                std::cout << "t2 woken up and (i != 2) = " 
                          << (var != 2) << std::endl;
            }
        }
    };

    std::thread t3{
        []{
            std::unique_lock<std::mutex> lck{M};
            while(var != 3){
                CV.wait(lck);
                std::cout << "t3 woken up and (i != 3) = " 
                          << (var != 3) << std::endl;
            }
        }
    };

    std::thread t4{
        []{
            std::unique_lock<std::mutex> lck{M};
            while(var != 4){
                CV.wait(lck);
                std::cout << "t4 woken up and (i != 4) = " 
                          << (var != 4) << std::endl;
            }
        }
    };

    for(int i = 0; i < 6; ++i){
        std::unique_lock<std::mutex> lck{M};
        var = i;
        CV.notify_all();
        lck.unlock();
        std::this_thread::sleep_for(std::chrono::seconds{1});
        std::cout << "\n\n";
    }

    t1.join();
    t2.join();
    t3.join();
    t4.join();
}  

这是结果

t3 woken up and (i != 3) = 1 //spurious wakeup


t3 woken up and (i != 3) = 1
t4 woken up and (i != 4) = 1
t2 woken up and (i != 2) = 1
t1 woken up and (i != 1) = 0


t3 woken up and (i != 3) = 1
t4 woken up and (i != 4) = 1
t2 woken up and (i != 2) = 0


t3 woken up and (i != 3) = 0
t4 woken up and (i != 4) = 1


t4 woken up and (i != 4) = 0

所以我很高兴地看到,无论第一次唤醒哪个线程,所有通知的线程都会以某种随机顺序唤醒,只需调用notify_all()。看起来当notify_all()(比如线程A)唤醒的线程之一在完成某些工作后解锁mutex 时,下一个线程通过对notify_all() 的相同调用唤醒,因为线程A 自动锁定与线程 A 刚刚解锁的 mutex 相同。这种情况一直持续到被notify_all() 唤醒的所有线程都锁定/解锁了用于保护共享数据的相同mutex

【讨论】:

    猜你喜欢
    • 2012-11-02
    • 2013-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-15
    • 2016-09-07
    • 2012-10-17
    相关资源
    最近更新 更多