东方耀AI技术分享

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 1337|回复: 1
打印 上一主题 下一主题

[C/C++] 多线程的条件变量,避免CPU空转:wait、notify_one、unique_lock

[复制链接]

1365

主题

1856

帖子

1万

积分

管理员

Rank: 10Rank: 10Rank: 10

积分
14439
QQ
跳转到指定楼层
楼主
发表于 2021-8-9 18:00:32 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
多线程的条件变量,避免CPU空转:wait、notify_one、unique_lock




#include <condition_variable>
条件变量:子线程可以更好的等待,避免cpu空转,提高性能效率
unique_lock是与条件变量一起使用的
unique_lock可以在中间过程中释放锁 而lock_guard做不到




http://www.ai111.vip/thread-1248-1-1.html 基础上改进

多线程编程中为啥这么多的锁?
std::lock_guard<std::mutex> lock(m_mutex);  //利用lock对象的构造与自动析构
std::unique_lock<std::mutex> lock(m_mutex);  // 与条件变量配合使用 避免CPU空转

typedef boost::unique_lock<boost::mutex> scoped_lock;
boost::lock_guard<boost::shared_mutex> guard(m_mutex);
boost::shared_lock<boost::shared_mutex> guard(m_mutex);


  1. #include <iostream>
  2. #include <list>
  3. #include <vector>
  4. #include <thread>
  5. #include <atomic>
  6. #include <mutex>
  7. #include <cstring>
  8. #include <string>
  9. #include <condition_variable>


  10. class Message
  11. {
  12. private:
  13.     std::string m_data;
  14. public:
  15.     Message(std::string d);
  16.     const std::string& data();
  17. };

  18. Message::Message(std::string d=std::string()) : m_data(d)
  19. {
  20. }

  21. const std::string& Message::data(){
  22.     return m_data;
  23. }

  24. std::list<Message> global_message_list;  //所有stl中的容器都是线程不安全的
  25. std::mutex global_mutex;
  26. std::atomic<bool> ready(false);  //原子操作变量
  27. std::atomic<bool> quit(false);
  28. std::atomic<int> total_size(0);

  29. std::condition_variable cv;

  30. //http://www.ai111.vip/thread-1247-1-1.html  继续改进 减少锁空间
  31. // 多线程编程:尽量减少锁空间

  32. // 条件变量:子线程可以更好的等待,避免cpu空转,提高性能效率


  33. void worker(int i){
  34.     while(!ready){
  35.         //没有准备好 就在循环中等待
  36.     }
  37.     Message msg;
  38.     while(!quit){  // 不退出
  39.         //std::this_thread::sleep_for(std::chrono::milliseconds(1));
  40.         
  41.         {   
  42.             //将锁空间 尽量压缩在这块代码内
  43.             //std::lock_guard<std::mutex> lock(global_mutex);  //利用lock对象的构造与自动析构
  44.             // unique_lock是与条件变量一起使用的
  45.             std::unique_lock<std::mutex> lock(global_mutex);
  46.             // 等待的过程中 把锁绑定的mutex释放掉 是不加锁的
  47.             // unique_lock可以在中间过程中释放锁 而lock_guard做不到
  48.             // 等待 lambda函数里的条件 达成 就被唤醒
  49.             cv.wait(lock, [](){
  50.                 //要么是 要退出 或 消息队列不为空
  51.                 return quit || !global_message_list.empty();
  52.             });
  53.             if(quit){
  54.                 return;
  55.             }
  56.             auto iter = global_message_list.begin();
  57.             msg = std::move(*iter);
  58.             //std ::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝
  59.             global_message_list.erase(iter);
  60.         }
  61.         //do real work and erase it!  干的工作:计算字符串的长度并求和
  62.         //真正干活的 有atomic原子操作 所以不需要 临界区mutex加锁了
  63.         total_size += strlen(msg.data().c_str());

  64.     }
  65. }


  66. // 发现:多线程比单线程的程序耗时还要多,原因何在?
  67. // 1、锁用得过于频繁    伪多线程

  68. int main(){
  69.     // 数据的准备工作
  70.     for(int i=0;i<10000;++i){
  71.         global_message_list.push_back("this is a test"+std::to_string(i));
  72.     }

  73.     // 创建多个线程来干活
  74.     const int threadCount = 4;
  75.     std::vector<std::thread> pool;
  76.     for(int i=0;i<threadCount;i++){
  77.         //pool.emplace_back(worker, i);
  78.         // 创建线程 非lambda函数方式
  79.         pool.emplace_back(std::thread(worker, i));
  80.     }
  81.     // 主线程给 每个子线程的 一个信号(命令):准备干活了
  82.     ready = true;
  83.    
  84.     // 主线程往全局list放数据
  85.     for(int i=0;i<10000;++i){
  86.         std::this_thread::sleep_for(std::chrono::milliseconds(1));
  87.         {
  88.             // 减少锁空间
  89.             std::lock_guard<std::mutex> lock(global_mutex);
  90.             global_message_list.push_back("the second "+std::to_string(i));
  91.         }
  92.         // 关键代码:每放一个新元素就通知一个子线程  与 cv.wait()对应的
  93.         cv.notify_one();
  94.         // global_message_list中没有元素就等待 有元素才运行 极大避免CPU空转
  95.     }

  96.     //quit=true;
  97.     while (true)
  98.     {
  99.         std::lock_guard<std::mutex> lock(global_mutex);
  100.         if(global_message_list.empty()){
  101.             // 主线程 发信号给所有的子线程
  102.             // 假设:所有子线程是要把活 全部干完的!
  103.             quit=true;
  104.             cv.notify_all();   //全部唤醒子线程  与 cv.wait()对应的
  105.             break;
  106.         }
  107.     }
  108.    


  109.     for(auto &v : pool){
  110.         if(v.joinable()){
  111.             v.join();
  112.         }
  113.     }

  114.     std::cout << "主线程结束!" << std::endl;
  115.     std::cout << "统计所有字符串的总长度=" << total_size << std::endl;

  116. //     统计所有字符串的总长度=327780

  117. // real        0m10.843s
  118. // user        0m13.613s
  119. // sys        0m25.811s

  120. // 主线程结束!
  121. // 统计所有字符串的总长度=327780

  122. // real        0m11.235s
  123. // user        0m0.475s
  124. // sys        0m0.486s


  125.     return 0;
  126. }

复制代码






让天下人人学会人工智能!人工智能的前景一片大好!
回复

使用道具 举报

0

主题

98

帖子

200

积分

中级会员

Rank: 3Rank: 3

积分
200
沙发
发表于 2021-11-23 19:32:43 | 只看该作者
让天下人人学会人工智能!人工智能的前景一片大好!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|人工智能工程师的摇篮 ( 湘ICP备2020019608号-1 )

GMT+8, 2024-5-19 13:29 , Processed in 0.177651 second(s), 18 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表