1、前言

在学习利用多线程来实现多模型的目标检测时,接触到了 condition_variable、future等多线程编程知识,感觉自己对这方面的知识还不熟悉,于是便找了一些学习资料进行学习。

2、学习内容

2.1、生产者消费者模型

如果只考虑一个生产者和一个消费者线程,生产者线程不停的在队列q中添加值,消费者线程不停的在队列中去除值,此时可以采用条件变量来唤醒消费者线程,当消费者线程判断队列为空时,释放锁,并cv.wait()。而生产者线程增加一个值后,就会使用cv.notify_one()随机的唤醒一个线程,因为这里只有一个线程,cv.notify_one() 和 cv.notify_all() 的功能一致。但是如果有多个消费者线程,则会出现问题。

#include<stdio.h>
#include<thread>
#include<queue>
#include<mutex>
#include<string>
#include<chrono>
#include<iostream>
#include<condition_variable>
#include<future>using namespace std;condition_variable cv;mutex mtx;
deque<int>q;void task1(){int i=0;while(true){unique_lock<mutex> lock(mtx);q.emplace_back(i);cv.notify_one();// cv.notify_all();//每次生产出一个数据后,就去唤醒 cv_waitif(i<999999){i++;}else{i=0;}}
}void task2(){int data = 0;while(true){unique_lock<mutex>lock(mtx);//如果队列为空,就利用wait进入休眠状态,同时释放锁,被唤醒时加锁if(q.empty()){cv.wait(lock);}if(!q.empty()){data = q.front();q.pop_front();cout<<"Get value from que:"<<data<<endl;}}
}void task2(){int data = 0;while(true){unique_lock<mutex>lock(mtx);//如果队列为空,就利用wait进入休眠状态,同时释放锁,被唤醒时加锁if(q.empty()){cv.wait(lock);}if(!q.empty()){data = q.front();q.pop_front();cout<<"Get value from que:"<<data<<endl;}}
}// void task3(){
//     int data = 0;
//     while(true){
//         unique_lock<mutex>lock(mtx);//         //如果队列为空,就利用wait进入休眠状态,同时释放锁,被唤醒时加锁
//         if(q.empty()){
//             cv.wait(lock);
//         }//         if(!q.empty()){
//             data = q.front();
//             q.pop_front();
//             cout<<"Get value from que:"<<data<<endl;
//         }
//     }
// }int main(){thread t1(task1);thread t2(task2);if(t1.joinable())t1.join();if(t2.joinable())t2.join();// if(t3.joinable())//   t3.join();return 0;
}

2.2、功能优化

考虑以下场景,当有一个生产者线程和两个消费者线程时,队列中只有一个元素,消费者线程2处理wiat状态,消费者1取出q中的元素正常进行打印,然后进入到下一个循环中并拿到锁,此时生产者线程增加一个元素,并随机的唤醒了消费者线程2,但是此时消费者线程拿到了锁并取出值并打印,此时消费者线程再去队列中拿值时就会引发异常,此时只需要将 其中的if修改为while,while会多次检查队列是否为空

#include<stdio.h>
#include<thread>
#include<queue>
#include<mutex>
#include<string>
#include<chrono>
#include<iostream>
#include<condition_variable>
#include<future>using namespace std;condition_variable cv;mutex mtx;
deque<int>q;//生产者线程
void prod(){int i=0;while(true){unique_lock<mutex> lock(mtx);q.emplace_back(i);//随机唤醒某一个线程cv.notify_one();// 去唤醒所有线程// cv.notify_all();//每次生产出一个数据后,就去唤醒 cv_waitif(i<999999){i++;}else{i=0;}}
}//消费者线程1
void con1(){int data = 0;while(true){unique_lock<mutex>lock(mtx);//如果队列为空,就利用wait进入休眠状态,同时释放锁,被唤醒时加锁while(q.empty()){//可能有多个线程调用了这个cv_wait,所以会导致报错cv.wait(lock);//含义如下,//lock.unlock()//cv.wait()}data = q.front();q.pop_front();cout<<"Get value from que:"<<data<<endl;}
}//消费者线程2
//如果此时增加了一个消费者线程,将会出现问题//虚假唤醒问题  即队列为空,但是消费者线程却被唤醒了,去取队列里面的值,就报错了、
//发生错误的情形如下:
//在线程1中,此时队列q中有一个值,线程1正常pop,然后打印,接着进入到下一个循环,运行到 unique_lock<mutex>lock(mtx);时
//此时队列 元素+1,然后随机的唤醒了线程2,但此时线程1拥有队列的锁,于是线程1拿到了队列中的值,然后进行的pop,此时队列为空,
//此时线程2再去拿队列中的值,就会发生异常,只需要将 if 修改为whilevoid con2(){int data = 0;while(true){unique_lock<mutex>lock(mtx);//如果队列为空,就利用wait进入休眠状态,同时释放锁,被唤醒时加锁while(q.empty()){cv.wait(lock);}data = q.front();q.pop_front();cout<<"Get value from que:"<<data<<endl;}
}int main(){thread t1(prod);thread t2(con1);thread t3(con2);if(t1.joinable())t1.join();if(t2.joinable())t2.join();if(t3.joinable())t3.join();return 0;
}

2.3 future、promise

如果需要获得线程中的值,可以采用条件变量等待子线程结束,再打印想要的值

#include<stdio.h>
#include<thread>
#include<queue>
#include<mutex>
#include<string>
#include<chrono>
#include<iostream>
#include<condition_variable>
#include<future>using namespace std;mutex mtx;
condition_variable cv;void task(int a,int b, promise<int>& ret){int ret_a = a * a;int ret_b = b * 2;ret = ret_a + ret_b;cv.notify_one();
}//这个版本导致代码太多,使用future可以优雅的解决int main(){int ret = 0;//如果需要传递引用,则需要加ref//将p传递进去thread t(task,1,2,ref(ret));unique_lock<mutex>lock(mtx);//等待线程通知,然后往下执行cv.wait(lock);cout<< "return value is" << ret<<endl;if(t.joinable())t.join();}

上述这种方式可以使用future更为优雅的解决

#include<stdio.h>
#include<thread>
#include<queue>
#include<mutex>
#include<string>
#include<chrono>
#include<iostream>
#include<condition_variable>
#include<future>using namespace std;void task(int a,int b,  promise<int>& ret){int ret_a = a * a;int ret_b = b * 2;ret.set_value(ret_a+ret_b);}//这个版本导致代码太多,使用future可以优雅的解决int main(){int ret = 0;promise<int>p;future<int>f = p.get_future(); //将f与p关联起来thread t(task,1,2,ref(p));//f.get()会阻塞在这里等待 p给f传递值,且f.get()只能用一次cout <<"return ret is :" << f.get() <<endl;if(t.joinable())t.join();}

如果使用多个子线程,但其中 f.get()只能只用一次,此时可以使用 share_future

#include<stdio.h>
#include<thread>
#include<queue>
#include<mutex>
#include<string>
#include<chrono>
#include<iostream>
#include<condition_variable>
#include<future>using namespace std;void task(int a, future<int>&b,  promise<int>& ret){int ret_a = a * a;//等待主线程的p_in 设置号值之后再继续int ret_b = b.get()* 2;ret.set_value(ret_a+ret_b);}//有时候 在给线程传值是,可能不需要立马传递值,可能需要过一段时间传递值int main(){int ret = 0;promise<int>p_ret;future<int>f_ret = p_ret.get_future();//延时传值promise<int>p_in;future<int>f_in = p_in.get_future();thread t(task,1,ref(f_in),ref(p_ret));//dop_in.set_value(8);//cout <<"return ret is :" << f_ret.get() <<endl;if(t.joinable())t.join();}

2.4、功能优化

上诉方法可以通过系统函数写的更为简洁

#include<stdio.h>
#include<thread>
#include<queue>
#include<mutex>
#include<string>
#include<chrono>
#include<iostream>
#include<condition_variable>
#include<future>using namespace std;int task(int a,int b){int ret_a = a * a;//等待主线程的p_in 设置号值之后再继续int ret_b = b * 2;return ret_a+ret_b;
}//为了简化上述方法,可以使用asyncint main(){//使用async可以在线程中获得返回值//async不一定创建新的线程做计算,如果使用launch::async则会开启新的线程//launch::deferred 为延迟调用,当有fu.get()时,才会开启线程future<int> fu = async(launch::async,task,1,2);cout <<"return ret is :" << fu.get() <<endl;}

3、总结

本次多多线程学习主要学习了条件变量、futrue、pormise的使用方法,希望在工作中处理多线程时,能够更优雅的解决问题。

【cpp学习笔记】多线程编程相关推荐

  1. python学习笔记——多线程编程

    python学习笔记--多线程编程 基础不必多讲,还是直接进入python. Python代码代码的执行由python虚拟机(也叫解释器主循环)来控制.Python在设计之初就考虑到要在主循环中,同时 ...

  2. Java学习笔记---多线程并发

    Java学习笔记---多线程并发 (一)认识线程和进程 (二)java中实现多线程的三种手段 [1]在java中实现多线程操作有三种手段: [2]为什么更推荐使用Runnable接口? [3][补充知 ...

  3. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  4. ufldl学习笔记与编程作业:Multi-Layer Neural Network(多层神经网络+识别手写体编程)...

    ufldl学习笔记与编程作业:Multi-Layer Neural Network(多层神经网络+识别手写体编程) ufldl出了新教程,感觉比之前的好,从基础讲起,系统清晰,又有编程实践. 在dee ...

  5. 学习笔记之编程达到一个高的境界就是自制脚本语言(图)

    学习笔记之编程达到一个高的境界就是自制脚本语言(图) 编程达到一个高的境界就是自制脚本语言,通过这可以精通编程里面的高深的技术,如编译原理.语言处理器.编译器与解释器,这些都是代表一个程序员实力的技术 ...

  6. Python学习笔记:网络编程

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  7. linux学习笔记 -- 系统编程

    系统编程 相关概念 概念 简易cpu结构 mmu内存管理单元 环境变量 PATH SHELL HOME LANG TERM getenv setenv unsetenv 进程控制 fork函数 get ...

  8. Java张孝祥视频 学习笔记 多线程

    /*************************************************************************************/ 此博客主要是在观看张孝祥 ...

  9. Go学习笔记—多线程

    多线程编程 ​ 一个进程可以包含多个线程,这些线程运行的一定是同一个程序(进程==程序),且都由当前进程中已经存在的线程通过系统调用的方式创建出来.进程是资源分配的基本单位,线程是调度运行的基本单位, ...

最新文章

  1. 洛谷2014选课(树型dp)
  2. VMware vSphere 7 服务器ESXi虚拟化HA高可用解决方案
  3. 7个示例科普CPU Cache
  4. jQ复制按钮的插件zclip
  5. 【笔记】springboot+spring security登录流程实现
  6. 挖洞技巧:支付漏洞之总结
  7. hibernate mysql 读写分离_SpringBoot集成Spring Data JPA及读写分离
  8. wordpress页面里可不可以写php,如何在WordPress页面中创建不同的可编辑部分?
  9. 技巧:让Eclipse或Flex Builder 支持自动换行。(转)
  10. C语言如何依次读取字符,C中的字符串,如何获取subString
  11. 主机管理+堡垒机系统开发:前端批量命令结果(十二)
  12. SEO关键词之选取策略及具体方法
  13. 学习W5500的OTA平台的SDK
  14. 甘谷2021年高考成绩查询入口,筑梦百天,赢在高考|甘谷一中2021届高考百日誓师大会...
  15. 【OCP|052】OCP最新题库解析(052)--小麦苗解答版
  16. 十年技术进阶路:让我明白了三件要事。关于如何做好技术 Team Leader?如何提升管理业务技术水平?(10000字长文)...
  17. SPP-net中的spatial pyramid pooling
  18. 安装python2.7安装方法_python2.7环境如何安装
  19. 7-2 N阶楼梯上楼问题 (15分) C++
  20. WinBUGS对多元随机波动率SV模型:贝叶斯估计与模型比较

热门文章

  1. 2021年汽车驾驶员(初级)实操考试视频及汽车驾驶员(初级)考试软件
  2. 探索大型网站技术架构(一)
  3. 在ARM-linux上实现4G模块PPP拨号上网
  4. 2021-10-26 小猴摘桃
  5. 抢救站位图片_抢救病人时护士的站位如何安排更合理? 教你这四种!
  6. 简单记录一下MOCTF的三道web题
  7. 利用GIZA++和Moses生成双向对齐文件
  8. linux sfdisk命令,Linux运维知识之Linux sfdisk硬盘分区工具程序命令详解
  9. 文献阅读(24)KDD2020-GCC:Graph Contrastive Coding for Graph Neural Network Pre-Training
  10. 操作系统课程设计---实验十 简单shell命令行解释器的设计与实现