1、简介

  C11提供另外一种用于等待的同步机制,它可以阻塞一个或者多个线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线程。条件变量要和互斥量配合起来使用。

  condition_variable,配合std::unique_lock<std::mutex>进行wait操作。
  condition_variable_any,和任意带有lock、unlock语意 的mutex搭配使用,比较灵活,但是效率比condition_variable低。

  条件变量的使用过程如下:

  a.拥有条件变量的线程获取互斥量。
  b.循环检查某个条件,如果条件不满足,则阻塞线程直到满足;如果条件满足,则向下执行。
  c.某个线程满足条件并执行完成之后,调用notify_one或者notify_all来唤醒一个或者多个线程。

2、实践

  可以用条件变量来实现一个同步队列,同步队列作为一个线程安全的数据共享区,经常用于线程之间的读取,比如半同步半异步线程池的同步队列。

  1. #include <iostream>
  2. #include <chrono>
  3. #include <thread>
  4. #include <mutex>
  5. #include<condition_variable>
  6. #include <list>
  7.  
  8. template<typename T>
  9. class SyncQueue
  10. {
  11. public:
  12. SyncQueue(int maxSize) :m_maxSize(maxSize){}
  13.  
  14. void Put(const T & t)
  15. {
  16. std::lock_guard<std::mutex> locker(m_mutex);
  17. while (IsFull())
  18. {
  19. std::cout << "缓冲区满了,需要等待..." << std::endl;
  20. m_notFull.wait(m_mutex);
  21. }
  22.  
  23. m_queue.push_back(t);
  24. m_notEmpty.notify_one();
  25. }
  26.  
  27. void Take(const T & t)
  28. {
  29. std::lock_guard<std::mutex> locker(m_mutex);
  30. while (IsEmpty())
  31. {
  32. std::cout << "缓冲区空了,需要等待..." << std::endl;
  33. m_notEmpty.wait(m_mutex);
  34. }
  35.  
  36. t = m_queue.front();
  37. m_queue.pop_front(t);
  38. m_notFull.notify_one();
  39. }
  40.  
  41. bool Empty()
  42. {
  43. std::lock_guard<std::mutex> locker(m_mutex);
  44. return m_queue.empty();
  45. }
  46.  
  47. bool Full()
  48. {
  49. std::lock_guard<std::mutex> locker(m_mutex);
  50. return m_queue.size() == m_maxSize;
  51. }
  52.  
  53. size_t Size()
  54. {
  55. std::lock_guard<std::mutex> locker(m_mutex);
  56. return m_queue.size();
  57. }
  58.  
  59. private:
  60. bool IsFull() const
  61. {
  62. return m_queue.size() == m_maxSize;
  63. }
  64.  
  65. bool IsEmpty() const
  66. {
  67. return m_queue.empty();
  68. }
  69.  
  70. private:
  71. std::list<T> m_queue;              //缓冲区
  72. std::mutex m_mutex;              //互斥量
  73. std::condition_variable_any m_notEmpty; //不为空的条件变量
  74. std::condition_variable_any m_notFull; //没有满的条件变量
  75. int m_maxSize;            //同步队列最大容量
  76. };

  这个队列中,没有满的情况下可以插入数据,如果满了,则会调用m_notFull阻塞线程等待,等待消费线程取出数据之后发出一个未满的通知,然后前面阻塞的线程会被唤醒继续往下执行;如果队列为空,不能取出数据,调用m_notEmpty来阻塞当前线程,等待插入数据的线程插入数据发出不为空的通知,唤醒被阻塞的线程,往下执行读出数据。

  条件变量的wait方法还有个重载方法,可以接受一个条件。

  1. std::lock_guard<std::mutex> locker(m_mutex);
  2. while (IsFull())
  3. {
  4. std::cout << "缓冲区满了,需要等待..." << std::endl;
  5. m_notFull.wait(m_mutex);
  6. }

  可以写为这样:

  1. std::lock_guard<std::mutex> locker(m_mutex);
  2. m_notFull.wait(locker, [this]{ return !IsFull();});

  两种写法都一样,后者代码更加简洁,条件变量先检查判断式是否满足条件,如果满足,重新获取mutex,结束wait,继续往下执行;如果不满足条件,则释放mutex,将线程置为waiting状态,继续等待。

  需要注意的是,wait函数会释放掉mutex,而lock_guard还拥有mutex,他只在出了作用域之后才会释放掉mutex,所以这时并不会释放,但是执行wait会提前释放,而在wait提前释放掉锁之后,会处于等待状态,在notify_one/all唤醒之后,会先获取mutex,相当于之前的mutex又获取到了,所以在出作用域的时候,lock_guard释放锁不会产生问题。

  在这种情况下,如果用unique_lock语意更准确,因为unique_lock不像lock_guard一样只能在析构的时候才能释放锁,它可以随时释放锁,在wait的时候让uniq_lock释放锁,语意更加准确。

  上述例子中,可以用unique_lock来替换掉lock_guard,condition_variable来替换掉condition_variable_any,会使代码更加清晰,效率也更高。

3、超时等待

  除了wait还可以使用超时等待函数std::condition_variable::wait_for和std::condition_variable::wait_until。

  与 std::condition_variable::wait() 类似,不过 wait_for 可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_for 返回,剩下的处理步骤和 wait() 类似。

  与 std::condition_variable::wait_for 类似,但是 wait_until 可以指定一个时间点,在当前线程收到通知或者指定的时间点 abs_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_until 返回,剩下的处理步骤和 wait_for() 类似。

  1. // condition_variable::wait_for example
  2. #include <iostream> // std::cout
  3. #include <thread> // std::thread
  4. #include <chrono> // std::chrono::seconds
  5. #include <mutex> // std::mutex, std::unique_lock
  6. #include <condition_variable> // std::condition_variable, std::cv_status
  7.  
  8. std::condition_variable cv;
  9.  
  10. int value;
  11.  
  12. void read_value() {
  13. std::cin >> value;
  14. cv.notify_one();
  15. }
  16.  
  17. int main ()
  18. {
  19. std::cout << "Please, enter an integer (I'll be printing dots): \n";
  20. std::thread th(read_value);
  21.  
  22. std::mutex mtx;
  23. std::unique_lock<std::mutex> lck(mtx);
  24. while (cv.wait_for(lck,std::chrono::seconds())==std::cv_status::timeout) {
  25. std::cout << '.' << std::endl;
  26. }
  27. std::cout << "You entered: " << value << '\n';
  28.  
  29. //等待th线程执行完
  30. th.join();
  31.  
  32. return ;
  33. }

  如果用wait_until,只需要将条件改为时间点即可:

  1. while (cv.wait_for(lck,std::chrono::seconds())==std::cv_status::timeout)
  2. while (cv.wait_until(lck, std::chrono::system_clock::now() +std::chrono::seconds()) == std::cv_status::timeout)

C11线程管理:条件变量的更多相关文章

  1. Java线程:条件变量、原子量、线程池等

    一.条件变量 条件变量实现了java.util.concurrent.locks.Condition接口,条件变量的实例化就是通过一个Lock对象上调用newCondition()方法获得的,这样条件 ...

  2. python线程的条件变量Condition的用法实例

      Condition 对象就是条件变量,它总是与某种锁相关联,可以是外部传入的锁或是系统默认创建的锁.当几个条件变量共享一个锁时,你就应该自己传入一个锁.这个锁不需要你操心,Condition 类会 ...

  3. linux Posix线程同步(条件变量) 实例

    条件变量:与互斥量一起使用,暂时申请不到某资源时进入条件阻塞等待,当资源具备时线程恢复运行 应用场合:生产线程不断的生产资源,并通知产生资源的条件,消费线程在没有资源情况下进入条件等待,一直等到条件信 ...

  4. pThreads线程(三) 线程同步--条件变量

    条件变量(Condition Variables) 参考资料:http://game-lab.org/posts/posix-thread-cn/#5.1 条件变量是什么? 条件变量为我们提供了另一种 ...

  5. Linux线程同步——条件变量

    互斥锁是用来给资源上锁的,而条件变量是用来等待而不是用来上锁的. 条件变量用来自动阻塞一个线程,直到某特殊情况发生为止. 通常条件变量和互斥锁同时使用. 和条件变量使用有关的几个重要函数: int p ...

  6. C11线程管理:原子变量&单调函数

    1.原子变量 C++11提供了原子类型std::atomic<T>,可以使用任意类型作为模板参数,使用原子变量就不需要使用互斥量来保护该变量,用起来更加简洁. 举个例子,如果要做一个计数器 ...

  7. C11线程管理:异步操作

    1.异步操作 C++11提供了异步操作相关的类,std::future.std::promise和std::package_task.std::future作为异步结果的传输通道,方便的获取线程函数的 ...

  8. C11线程管理:线程创建

    1.线程的创建 C11创建线程非常简单,只需要提供线程函数就行,标准库提供线程库,并可以指定线程函数的参数. #include <iostream> #include <thread ...

  9. C11线程管理:互斥锁

    1.概述 锁类型 c11提供了跨平台的线程同步手段,用来保护多线程同时访问的共享数据. std::mutex,最基本的 Mutex 类,独占的互斥量,不能递归使用. std::time_mutex,带 ...

随机推荐

  1. Team Work总结 && OPP课程总结

    团队作业总结 工作总结 本次大作业我在团队内的工作是:根据框架构建实现建筑类的功能,包括防御塔.水晶.泉水等建筑.根据架构框架以及结合各建筑的特点,利用继承和多态很快速的解决了一些基本的问题.然而在实 ...

  2. 严重: Failed to destroy end point associated with ProtocolHandler ["http-nio-8080"] java.lang.NullPointer

    刚接触servlet类,按照课本的方法使用eclipse新建了一个servlet类. 新建完成后,在web.xml里面进行注册 这时候就会报错了. 五月 07, 2016 11:23:28 上午 or ...

  3. QSerialPort-Qt串口通讯

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QSerialPort-Qt串口通讯     本文地址:http://techieliang. ...

  4. 【转】MySQL数据表中记录不存在则插入,存在则更新

    mysql 记录不存在时插入在 MySQL 中,插入(insert)一条记录很简单,但是一些特殊应用,在插入记录前,需要检查这条记录是否已经存在,只有当记录不存在时才执行插入操作,本文介绍的就是这个问 ...

  5. [C/C++] 快速幂讲解

    转自:http://www.cnblogs.com/CXCXCXC/p/4641812.html 快速幂这个东西比较好理解,但实现起来到不老好办,记了几次老是忘,今天把它系统的总结一下防止忘记. 首先 ...

  6. Python常忘的进阶知识(下)

    0.目录 1.装饰器 1.1 为每个函数都增加一个功能 1.2 装饰器只是一种模式 1.3 语法糖 1.4 函数需要传递参数,该如何更改装饰器? 1.5 函数需要传递关键字参数,该如何更改装饰器? 2 ...

  7. WordPress忘记密码找回登录密码的四种行之有效的方法

    WordPress忘记密码找回登录密码的四种行之有效的方法 PS:20170214更新,感谢SuperDoge同学提供的方法,登入phpMyAdmin后,先从左边选自己的数据库,然后点上面的 SQL ...

  8. hive 分区表和分桶表

    1.创建分区表 hive> create table weather_list(year int,data int) partitioned by (createtime string,area ...

  9. 3.7 TCP拥塞控制

    3.7 TCP拥塞控制 在3.5.5流量控制中有,接收方通过维护一个rwnd来控制流量,本节中考虑三个问题: 第一,  一个TCP发送方如何限制它向其他连接发送流量的速率. 第二,  一个TCP发送方 ...

  10. NOIP2017 【游记】

    一年过去,想起去年还是个傻b[今年也是],心里总是无限的感慨. 脑海里是日日夜夜在机房的身影,一题一题AC的激情 我等今年等了许久,虽然我是个蒟蒻,但我有梦想的憧憬 鲲鹏展翅翼向天,扶摇直上九万里. ...