• C++ 多线程 condition变量


    首先condition变量的常用函数有两个:wait和notify_one[all]:

    wait函数:

    1. void wait(unique_lock& _Lck)
    2. { // wait for signal
    3. // Nothing to do to comply with LWG 2135 because std::mutex lock/unlock are nothrow
    4. _Cnd_waitX(_Mycnd(), _Lck.mutex()->_Mymtx());
    5. }

    wait函数接收一个unique_lock,并将其unlock,然后阻塞线程,注意,这点有点怪异,因为unlock总是与解封线程相关联,但这里却是与阻塞线程相关联,这是因为condition变量是一个提供不依赖锁来阻塞线程的工具,但不依赖锁,却为什么要传递一个锁呢?很简单,这是为了防止死锁,因为在wait前,可能该线程获取了多个或一个锁,这回导致在申请该锁的线程与正在wait且获得该锁的线程一起等待,这是我们不希望看见的,所以为了防止这种事情发生,我们需要将该线程的获得的锁传递给condition的wait函数,其中会调用该锁的unlock函数,然后进行等待,直到有线程将其唤醒;

    wait_for、wait_until

    这两个函数可以放到一起来说,因为提供的功能本质是相同的;

    先看源码:

    1. template<class _Rep,class _Period>
    2. cv_status wait_for(unique_lock& _Lck,
    3. const chrono::duration<_Rep, _Period>& _Rel_time)
    4. {
    5. // wait for duration
    6. STDEXT threads::xtime _Tgt = _To_xtime(_Rel_time);
    7. return (wait_until(_Lck, &_Tgt));
    8. }

    和wait函数一样,接受一个唯一锁,然后是一个时间变量,代表要等待的时间,最后返回一个cv_status代表等待的状态,看cv_status的源码:

    1. enum class cv_status {
    2. // names for wait returns
    3. no_timeout,
    4. timeout
    5. };

    一个代表的是在等待的过程中,该线程被唤醒,即no_timeout,另一个相反,即timeout;

    wait_until也是一个样子,只不过第二个参数相对与wait_for来说变了罢了;

    1. template<class _Clock,class _Duration>
    2. cv_status wait_until(unique_lock& _Lck,
    3. const chrono::time_point<_Clock, _Duration>& _Abs_time)
    4. { // wait until time point
    5. _STDEXT threads::xtime _Tgt = _To_xtime(_Abs_time - _Clock::now());
    6. return (wait_until(_Lck, &_Tgt));
    7. }

    唤醒函数:

    notify_one:

    假设我们定义了一个condition变量cond,当我们调用cond.notify_one函数的时候,会唤醒一个托管于condition变量的线程[当我们在某一个线程用同一个实例cond进行wait时,该线程就被托管与cond了],当然,被唤醒的是哪一个线程这个结果是不确定的,但是所有线程都会争抢这个机会,即会导致惊群效应;

    notify_all:

    这个看名字就可以知道了,这个是将托管与cond对象的线程全部唤醒:

    两段测试代码:

    1:使用notify_none:

    1. #include
    2. #include
    3. #include
    4. #include
    5. class testClass {
    6. std::mutex mtx;
    7. std::condition_variable cond;
    8. std::once_flag once;
    9. public:
    10. void call() {
    11. std::cout << "call: 多个线程进入fun1,但是锁被fun2占用,等待输入将锁释放" << std::endl;
    12. }
    13. void fun1(int i) {
    14. std::call_once(once,&testClass::call,this);
    15. std::this_thread::sleep_for(std::chrono::milliseconds(100));
    16. std::lock_guard lock_g(mtx);
    17. std::cout << "fun1: " << i << std::endl;
    18. }
    19. void fun2() {
    20. std::unique_lock u_lock(mtx);
    21. int x; std::cin >> x;
    22. std::cout << "fun2: 调用wiat方法,阻塞线程的同时将锁解开" << std::endl;
    23. cond.wait(u_lock);
    24. std::cout << "fun2: 解锁完成" << std::endl;
    25. }
    26. void fun3() {
    27. std::cout << "fun3: 输入任意字符调用notify函数,这将结束fun2的等待" << std::endl;
    28. int x; std::cin >> x;
    29. cond.notify_one();
    30. }
    31. };
    32. int main() {
    33. testClass test;
    34. std::thread thSon(&testClass::fun2,&test);
    35. std::thread thSons[10];
    36. for (int i = 0; i < 10; i++) {
    37. thSons[i] = std::thread(&testClass::fun1,&test, i);
    38. }
    39. for (int i = 0; i < 10; i++) {
    40. thSons[i].join();
    41. }
    42. std::thread thSon1(&testClass::fun3,&test);
    43. thSon.join();
    44. thSon1.join();
    45. }

    2:使用notify_all函数

    1. #include
    2. #include
    3. #include
    4. #include
    5. class testClass {
    6. std::mutex mtx;
    7. std::mutex mtx1;
    8. int id = 0;
    9. int id1 = 0;
    10. std::condition_variable cond;
    11. public:
    12. void print() {
    13. std::unique_lock lock_u(mtx);
    14. cond.wait(lock_u);
    15. std::cout << "print id " << id << std::endl << '\n';
    16. id++;
    17. }
    18. void print_() {
    19. std::unique_lock lock_u(mtx1);
    20. cond.wait(lock_u);
    21. std::cout << "prtint id" << id1 << std::endl << '\n';
    22. id1++;
    23. }
    24. void go() {
    25. cond.notify_all();
    26. }
    27. };
    28. int main() {
    29. testClass test;
    30. std::thread thSon[10];
    31. for (int i = 0; i < 10; i++) {
    32. if(i<5)
    33. thSon[i] = std::thread(&testClass::print, &test);
    34. else
    35. thSon[i] = std::thread(&testClass::print_, &test);
    36. }
    37. std::cout << "输入一个字符,启动唤醒函数" << std::endl;
    38. int x; std::cin >> x;
    39. test.go();
    40. for (int i = 0; i < 10; i++) {
    41. thSon[i].join();
    42. }
    43. }

    注意点:

    1:当一个线程被wake时,中线程会将获得的锁上锁,使线程的状态回到wait前

  • 相关阅读:
    一文教你如何使用Node进程管理工具-pm2
    Linux 搭建MQTT服务器
    lc回溯1
    Redis集群搭建
    PDFView4NET 11.0.6 Windows Forms and WPF
    Linux 内核参数:extra_free_kbytes
    MySQL备份与恢复
    【数据结构】波兰式、逆波兰式与中缀表达式
    单行函数,聚合函数课后练习
    Java中「Future」接口详解
  • 原文地址:https://blog.csdn.net/weixin_62953519/article/details/127558968