• C++11 条件变量


    目录

    条件变量

    1 成员函数 

    wait函数

    wait_for函数

    wait_until函数

    notify_one函数

    notify_all函数

    2 Demo1

    sync_queue.h

    condition-sync-queue.cpp

    3 Demo2

    2-sync_queue.h

    2-condition-sync-queue.cpp


    C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

    条件变量

            互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。但单靠互斥量无法实现线程的同步。

            线程同步是指线程间按照预定的先后次序顺序进行的行为。

          C++11对这种行为也提供了有力的支持,这就是条件变量。条件变量位于头文件condition_variable下。

    http://www.cplusplus.com/reference/condition_variable/condition_variable

    条件变量使用过程:

    1. 拥有条件变量的线程获取互斥量;

    2. 循环检查某个条件,如果条件不满足则阻塞直到条件满足;如果条件满足则向下执行;

    3. 某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或者所有等待线程。

    条件变量提供了两类操作:wait和notify。这两类操作构成了多线程同步的基础。

    1 成员函数 

    wait函数

    1. 函数原型
    2. void wait (unique_lock<mutex>& lck);
    3. template <class Predicate>
    4. void wait (unique_lock<mutex>& lck, Predicate pred);

    包含两种重载,第一种只包含unique_lock对象,另外一个Predicate 对象(等待条件),这里必须使用unique_lock,因为wait函数的工作原理:
    1.当前线程调用wait()后将被阻塞并且函数会解锁互斥量,直到另外某个线程调用notify_one或者notify_all唤醒当前线程;一旦当前线程获得通知(notify),wait()函数也是自动调用lock(),同理不能使用lock_guard对象


    2.如果wait没有第二个参数,第一次调用默认条件不成立,直接解锁互斥量并阻塞到本行,直到某一个线程调用notify_one或notify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后无条件地继续进行后面的操作。


    3.如果wait包含第二个参数,如果第二个参数不满足,那么wait将解锁互斥量并堵塞到本行,直到某一个线程调用notify_one或notify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后继续判断第二个参数,如果表达式为false,wait对互斥量解锁,然后休眠,如果为true,则进行后面的操作。

    wait_for函数

    1. 函数原型:
    2. template <class Rep, class Period>
    3. cv_status wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time);
    4. template <class Rep, class Period, class Predicate>
    5. bool wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);

            和wait不同的是,wait_for可以执行一个时间段,在线程收到唤醒通知或者时间超时之前,该线程都会处于阻塞状态,如果收到唤醒通知或者时间超时,wait_for返回,剩下操作和wait类似。 

    wait_until函数

    1. 函数原型:
    2. template <class Clock, class Duration>
    3. cv_status wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time);
    4. template <class Clock, class Duration, class Predicate>
    5. bool wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time,Predicate pred);

            与wait_for类似,只是wait_until可以指定一个时间点,在当前线程收到通知或者指定的时间点超时之前,该线程都会处于阻塞状态。如果超时或者收到唤醒通知,wait_until返回,剩下操作和wait类似

    notify_one函数

    1. 函数原型:
    2. void notify_one() noexcept;

    解锁正在等待当前条件的所有线程,如果没有正在等待的线程,则函数不执行任何操作。

    notify_all函数

    1. 函数原型:
    2. void notify_one() noexcept;

    解锁正在等待当前条件的所有线程,如果没有正在等待的线程,则函数不执行任何操作。

     

    2 Demo1

    使用条件变量实现一个同步队列,同步队列作为一个线程安全的数据共享区,经常用于线程之间数据读取。 

    sync_queue.h

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

    condition-sync-queue.cpp

    1. #include <iostream>
    2. #include "3-1-sync_queue.h"
    3. #include <thread>
    4. #include <iostream>
    5. #include <mutex>
    6. using namespace std;
    7. SyncQueue<int> syncQueue(5);
    8. void PutDatas()
    9. {
    10. for (int i = 0; i < 20; ++i)
    11. {
    12. syncQueue.Put(i);
    13. }
    14. std::cout << "PutDatas finish\n";
    15. }
    16. void TakeDatas()
    17. {
    18. int x = 0;
    19. for (int i = 0; i < 20; ++i)
    20. {
    21. syncQueue.Take(x);
    22. std::cout << x << std::endl;
    23. }
    24. std::cout << "TakeDatas finish\n";
    25. }
    26. int main(void)
    27. {
    28. std::thread t1(PutDatas); // 生产线程
    29. std::thread t2(TakeDatas); // 消费线程
    30. t1.join();
    31. t2.join();
    32. std::cout << "main finish\n";
    33. return 0;
    34. }

    代码中用到了std::lock_guard,它利用RAII机制可以保证安全释放mutex

    1. std::lock_guard<std::mutex> locker(_mutex);
    2. while (IsFull())
    3. {
    4. std::cout << "full wait..." << std::endl;
    5. _notFull.wait(_mutex);
    6. }

    可以改成 

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

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


            这里需要注意的是,wait函数中会释放mutexlock_guard这时还拥有mutex,它只会在出了作用域之后才会释放mutex,所以这时它并不会释放,但执行wait时会提前释放mutex

            从语义上看这里使用lock_guard会产生矛盾,但是实际上并不会出问题,因为wait提前释放锁之后会处于等待状态,在被notify_one或者notify_all唤醒后会先获取mutex这相当于lock_guardmutex在释放之后又获取到了,因此,在出了作用域之后lock_guard自动释放mutex不会有问题。这里应该用unique_lock,因为unique_lock不像lock_guard一样只能在析构时才释放锁,它可以随时释放锁,因此在wait时让unique_lock释放锁从语义上更加准确。

            使用unique_lockcondition_variable_variable改写,改写为用等待一个判断式的方法来实现一个简单的队列。 

     

    3 Demo2

    2-sync_queue.h

    1. #ifndef SIMPLE_SYNC_QUEUE_H
    2. #define SIMPLE_SYNC_QUEUE_H
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. template<typename T>
    9. class SimpleSyncQueue
    10. {
    11. public:
    12. SimpleSyncQueue(){}
    13. void Put(const T& x)
    14. {
    15. std::lock_guard locker(_mutex);
    16. _queue.push_back(x);
    17. _notEmpty.notify_one();
    18. }
    19. void Take(T& x)
    20. {
    21. std::unique_lock locker(_mutex);
    22. _notEmpty.wait(locker, [this]{return !_queue.empty(); });
    23. x = _queue.front();
    24. _queue.pop_front();
    25. }
    26. bool Empty()
    27. {
    28. std::lock_guard locker(_mutex);
    29. return _queue.empty();
    30. }
    31. size_t Size()
    32. {
    33. std::lock_guard locker(_mutex);
    34. return _queue.size();
    35. }
    36. private:
    37. std::list _queue;
    38. std::mutex _mutex;
    39. std::condition_variable _notEmpty;
    40. };
    41. #endif // SIMPLE_SYNC_QUEUE_H

    2-condition-sync-queue.cpp

    1. #include <iostream>
    2. #include <thread>
    3. #include <iostream>
    4. #include <mutex>
    5. #include "3-2-sync_queue2.h"
    6. using namespace std;
    7. SimpleSyncQueue<int> syncQueue;
    8. void PutDatas()
    9. {
    10. for (int i = 0; i < 20; ++i)
    11. {
    12. syncQueue.Put(888);
    13. }
    14. }
    15. void TakeDatas()
    16. {
    17. int x = 0;
    18. for (int i = 0; i < 20; ++i)
    19. {
    20. syncQueue.Take(x);
    21. std::cout << x << std::endl;
    22. }
    23. }
    24. int main(void)
    25. {
    26. std::thread t1(PutDatas);
    27. std::thread t2(TakeDatas);
    28. t1.join();
    29. t2.join();
    30. std::cout << "main finish\n";
    31. return 0;
    32. }
  • 相关阅读:
    MapStruct的一些常规用法
    Class Semantics-based Attention for Action Detection CSA论文阅读笔记
    Postman使用总结2
    2024最新的,免费的 ChatGPT 网站AI(八个)
    Harmony系统更改手机IP
    单商户商城系统功能拆解19—订单管理
    LayUI之CRUD
    Magnet: Push-based Shuffle Service for Large-scale Data Processing
    lvgl v8 linux下使用xmake交叉编译移植
    最小年龄仅5岁!盘点全球最“天才”少年黑客 TOP 10
  • 原文地址:https://blog.csdn.net/kakaka666/article/details/127930368