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. louvain算法_单细胞聚类(四)图解Leiden算法对Louvain算法的优化
  2. 每天一个linux命令(53)--ps命令
  3. 捡到vivo手机怎么清除账号_为什么现在买手机,很少会去考虑OPPO和vivo呢?看一下老板怎么说...
  4. 《从问题到程序:用Python学编程和计算》——导读
  5. android动画入门,Android动画之入门篇(一)
  6. windows无法打开所需的文件C:\Sources\install.wim。
  7. html 字体思源_CSS3嵌入字体@font-face调用字体(思源宋体regula/PingFang SC/ttf/woff)...
  8. 2021云上智能白皮书 附下载
  9. 计算机恶搞bat代码,电脑重启bat代码怎么设置 电脑整人bat代码大全
  10. Spring Boot使用JSP模板引擎
  11. java 股票数据接口_股票数据查询接口
  12. HTML+CSS+JS新年倒计时(实时更新)
  13. matlab实现简单图形的识别二
  14. 重磅!python获取同步输出的桌面网易云音乐歌词(内存偏移获取)
  15. 使用什么软件可以将PDF文件进行编辑
  16. 实例011 阳阳买苹果
  17. 招聘网探究分析报告(以描述性分析为主)
  18. 工作意向及规划个人建议
  19. 现代社会科学的优选法:谈取向运作法
  20. 群晖3617可以有几个网卡_星际蜗牛C款i211网卡服务器装Windows sevse 2012 R2服务器装机上...

热门文章

  1. 音频UGC内容推荐系统的思考
  2. mysql 存储过程 commit_mysql 存储过程commit
  3. 【渝粤教育】国家开放大学2019年春季 1127实用卫生统计学 参考试题
  4. 常用文件类型 -- INI文件介绍
  5. 使用 sed / shell 读取 ini 文件
  6. Linux shell--sfdisk manual
  7. 有什么好用的提词器工具?这四个提词器告别演讲烦恼
  8. HSSFWorkbook poi创建锁定的单元格
  9. Python学习笔记 - Python语法基础
  10. 全球智慧城市峰会“论道”杭州梦想小镇