用进行多线程开发

小时候,老师总是教育我们上课要专心,“一心不可二用”。可是CPU这个不听话的“熊孩子”偏偏却在一个芯片中加入了两个甚至多个运算核心,想要一“芯”二用。从硬件厂商的角度,通过增加CPU的运算核心,突破了原来单核CPU的频率极限,确实可以很大程度上增加CPU的总频率。在他们看来,这简直就是一个天才的创意。可是从软件厂商的角度,CPU运算核心的增加,并没有显著地提高软件的性能表现,有时候甚至会降低软件的性能。在他们看来,这无疑是一场噩梦的开始。

在以往的计算机发展历史中,硬件技术的发展,特别是CPU频率的不断提升,总是同时会提升软件的性能。更重要的是,这种性能的提升是完全免费的,软件什么都不需要做,只要换上新出来的更高频率的CPU,软件就获得更好的性能表现。从286到486,从奔腾到酷睿,每次CPU频率的提升,无不给软件性能带来大幅提升。如果有用户抱怨我们的软件性能不佳,我们也无须着急,只需要坐等Intel或者AMD推出更高频率的CPU并让用户换上就可以了。

但是,当CPU的发展进入多核时代以后,程序员们沮丧地发现,CPU长期提供的这份“免费的午餐”消失了。这是因为当前大多数程序几乎都是针对一个运算核心的CPU而设计的单线程程序。虽然CPU有多个运算核心,但它却只能在其中的某一个运算核心上进行运算,而其他的运算核心并没有得到利用而白白浪费了。虽然CPU的运算核心增加了,总的频率增加了,但是单个运算核心的频率并没有太大变化,所以程序的性能并没有随着CPU运算核心的增加而得到显著的提升。

从单核CPU到多核CPU

浪费是可耻的,更何况我们浪费的是如此宝贵的CPU运算资源。为了把被浪费的CPU运算资源充分地利用起来,唯一的办法就是针对CPU的多个运算核心设计我们的程序,通过将原来由单个线程串行执行的程序并行化,用多线程代替原来的单线程,使其可以同时运行在多个运算核心上,以此来实现对CPU多个运算核心的充分利用。这样,程序的性能又会随着CPU频率的提高而得到提升。

可是,多线程程序的设计并不是一个轻松简单的活儿。在C++11之前,如果我们想要实现一个多线程程序,我们需要使用系统API创建线程,需要小心地维护对共享资源的访问等等。更折磨人的是,多线程增加了程序的设计与实现难度,如果设计错误,多线程不仅不会提升程序的性能,反而可能会降低性能,甚至引起资源互锁而导致程序失去响应。为了简化多线程程序的设计与实现,C++11的标准库专门提供了头文件以支持多线程程序的开发,而那份美味的“免费的午餐”也正在回到我们面前。

利用thread创建线程

C++11中的头文件提供了thread、mutex以及unique_lock等基本对象来对多线程开发中最常用的线程、互斥以及锁等基本概念进行抽象与表达,为多线程程序的实现提供了一个较低抽象层次的编程模型。其中,最基础也最重要的是与线程概念相对应的thread类。线程是对程序中的某个执行或者计算过程的一种表述,而所谓的多线程程序,就是将原来的一个执行过程分成多个过程去执行。由此可见,线程的创建,是实现多线程的基础。可是在以往,要想在程序中创建一个线程,我们不得不针对不同的操作系统调用不同的系统函数,然后还要提供复杂的参数才能完成线程的创建。幸运的是,thread类的出现,大大地简化了这一过程。

thread类对线程概念进行了很好的抽象与实现,从而使得我们可以非常简单地使用一个函数指针(也包括函数对象和Lambda表达式)来构建一个thread对象。而一旦拥有了thread对象,就意味着我们创建了一个线程,也就可以利用thread对象所提供的成员函数对这个线程进行调度,启动、挂起或者停止这个线程,以操作线程完成某个执行过程。例如:

#include // 引入定义thread类的头文件#include  // 使用thread所在的名字空间using namespace std; // 定义需要线程执行的函数和函数对象void ListenMusic(){cout<

在这段代码中,我们利用函数对象类ReadBook的一个read函数对象和指向ListenMusic()函数的函数指针(也就是它的函数名)作为thread类构造函数的参数,分别创建了readthread和listenthread这两个thread对象。thread对象的创建,意味着它将创建新的线程,并开始执行作为构造函数参数传递给thread对象的函数对象或者是函数,通过简单的一个步骤,就完成了线程的创建与启动执行。在多线程环境下,我们将执行主函数并负责创建其它线程的线程称为主线程,而那些被创建的线程则相应地被称为分支线程或工作者线程,其执行的函数则被称为线程函数。在执行的时候,主线程开始进入主函数执行,通过创建两个thread对象而创建了两个分支线程并立即启动执行其线程函数,而与此同时,执行主函数的主线程将继续向下执行。这样,主线程和两个分支线程同时都在执行,操作系统会将它们调度到CPU的多个运算核心去执行,以此达到对CPU多个运算核心的充分利用。当主线程遇到thread对象调用的join()函数后,主线程将等待这个thread对象执行完毕之后,再继续往下执行,直到最后主函数执行完毕,退出整个程序。整个程序的执行流程如下图12-4所示:

多线程程序的执行流程

除了利用thread对象创建新的线程简单地执行某个线程函数之外,就像我们常常需要通过参数与普通函数进行数据传递一样,更多时候,我们也需要向线程函数内传入数据以供其进行处理,或者是从线程函数中传出结果数据。要做到这一点,我们同样需要给线程函数加上参数,跟普通函数类似,如果只是需要向线程函数内传入数据,那就加上传值形式的参数,而如果加上传指针和传引用形式的参数,则既可以传入也可以传出数据。与普通函数在调用时将实际参数复制给函数的形式参数所不同的是,线程函数的形式参数的赋值是在这个函数被用于创建thread对象时完成的。当我们在使用某个带有参数的线程函数创建thread对象时,在thread构造函数的实际参数中,我们不仅要用这个函数指针或者函数对象做第一个参数,同时其后还要依次加上线程函数所需要的各个实际参数。在创建thread对象的时候,这些实际参数会被拷贝复制给线程函数相应的形式参数,以此来实现数据的传递。这里需要注意的是,如果线程函数的参数是传引用形式,那么在创建thread对象的时候,我们需要使用ref()函数获得实际参数的引用才行,否则,即使这个参数是引用形式,它也会被拷贝复制而在线程函数和本地函数间形成两个副本,起不到传出数据的效果。例如,我们需要向上面的ListenMusic()线程函数传入歌曲名并从中传出结果数据:

// 需要传递数据的线程函数// 传值形式的strSong负责向线程函数内传入数据// 传引用形式的vecEar负责向线程函数外传出数据void ListenMusic(string strSong,vector& vecEar){ cout< vecEar; // 用于保存结果数据的容器 string strSong = "歌唱祖国"; // 传入线程函数的数据 // 在创建thread对象时传递数据// 第一个参数是线程函数指针,其后依次是线程函数所需要的参数thread listenthread(ListenMusic,strSong,ref(vecEar)); // …listenthread.join();  // 输出结果数据 for(string strName : vecEar) cout<

利用thread对象,我们可以像调用普通函数一样简单地用thread对象创建另外一个线程来执行我们的线程函数,也可以像与普通函数传递数据一样简单地与线程函数传递数据,从此,一边听着歌还可以一边看着书,轻松开启我们惬意的“一芯二用”的并行生活。

知道更多:线程中的瞌睡虫

在利用线程执行某个任务的时候,我们往往要对线程的执行时间进行控制,让线程在等待一定时间之后再继续执行,或者是在某个事先设定的固定时间点之后执行。这时,我们就需要用到std::this_thread名字空间下的sleep_for()函数和sleep_until()函数来完成对线程执行状态的时间控制了。

sleep_for()函数可以让当前线程(也就是调用这个函数的线程)暂停执行一段时间,等过了这段时间之后再继续恢复执行;而sleep_until()函数则是让当前线程一直暂停,直到某个固定时间点的到来才会继续恢复执行。它们就像两条瞌睡虫,一条可以让线程瞌睡一整天(固定时间段),而另一条更厉害,可以让线程一直瞌睡到天明(固定时间点)。对于我们来说,瞌睡虫很是讨厌,可是对于线程来说,瞌睡虫却是大有用处。

比如,我们想要模拟一下传说中的2012世界末日,就需要这两条瞌睡虫来让线程瞌睡瞌睡:

#include #include  // 引入线程相关的头文件#include  // 引入时间相关的头文件 using namespace std;using namespace std::chrono; // 使用时间相关的名字空间 int main(){// 构造一个固定时间点:2012年 12月21日零时 tm timeinfo = tm(); timeinfo.tm_year = 112; // 年: 2012 = 1900 + 112timeinfo.tm_mon = 11; // 月:12 = 1 + 11 timeinfo.tm_mday = 21; // 21日time_t tt = mktime(&timeinfo);// 利用time_t类型的变量tt创建一个表示世界末日固定时间点的time_point对象tp system_clock::time_point tp = system_clock::from_time_t (tt); // 当前线程一直瞌睡到tp表示的2012年12月21日零时this_thread::sleep_until(tp);  // 世界末日到了,程序继续恢复执行,响铃10次发出警报for(int i = 0; i < 10; ++i){cout<

在两条瞌睡虫的合作下,我们的模拟程序会首先在sleep_until()的作用下,瞌睡(暂停执行)到tp所表示的固定时间点(2012年12月21日零时),等到了这个时间点后,程序才会恢复执行。紧接着,程序执行进入一个for循环,每次循环,它都会输出一个计算机响铃,然后又会在sleep_for()这条瞌睡虫的作用下,休眠一秒钟,然后再继续执行下一次循环。整个程序的效果就是到了世界末日,这个程序会发出滴滴滴的警报,告诉我们,世界末日来了,赶紧逃命吧!可是,可是,2012早都过去了,虽然世界末日没来,可这个程序却忠实地准时发出了警报。由此可见,玛雅人忽悠人,还是C++程序更可信。

主线程是如何向子线程传递数据的?_c++ 利用thread创建线程相关推荐

  1. OS / Linux / 主线程退出了,子线程会退出吗?

    在 linux 世界中,主线程退出了,子线程是否退出是要看主线程的退出方式. 主线程以 return 的方式退出. 主线程以 pthread_exit() 函数的方式退出. 前者,main() 执行完 ...

  2. pthread线程传递数据回主线程_操作系统4:线程(1)

    接下来讨论下线程.进程和线程是一个很有趣的话题,进程和线程的区别到底是什么?一些书上讲线程是"轻量级进程",从而可以节省切换开销.但是线程到底是怎么样成为轻量级进程的呢? 可以设想 ...

  3. 面试官:如何让主线程等待所有的子线程执行结束之后再执行

    java 主线程等待所有子线程执行完毕在执行,在工作总往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完后再把那段逻辑的处理结果进行汇总(比如用户下单一个产品,后台会做一系列的处理,为了提高 ...

  4. InheritableThreadLocal类原理简介使用 父子线程传递数据详解 多线程中篇(十八)...

    上一篇文章中对ThreadLocal进行了详尽的介绍,另外还有一个类: InheritableThreadLocal 他是ThreadLocal的子类,那么这个类又有什么作用呢? 测试代码 publi ...

  5. JAVA跨线程传递数据方式总结

    实现跨线程传递数据方式: v1:子线程使用主线程的局部变量 这种当主线程和子线程不在一快儿时就不适用.可以使用JDK原生的InheritableThreadLocal. v2:InheritableT ...

  6. 向线程传递数据与线程用回调方法检索数据

    用回调方法检索数据 /**//*用回调方法检索数据 下面的示例演示了一个从线程中检索数据的回调方法.包含数据和线程方法的类的构造函数也接受代表回调方法的委托: 在线程方法结束前,它调用该回调委托. * ...

  7. vue子组件获取父组件数据_在vue.js中父组件是如何向子组件传递数据的?

    本文只有一个学习点. 父组件如何向子组件传递数据. 一起学习,更多文章请关注我的头条号,我是落笔承冰. 一.先创建一张空白网页index.html,在head标签里设置好vue的链接库. 二.写一个绑 ...

  8. vue 父向子组件传递数据,子组件向父组件传递数据方式

    父组件向子组件传递数据通过props,子组件引入到父组件中,设置一个值等于父组件的数据,通过:bind将数据传到子组件中,子组件中通过props接收父组件的数据,这样就可以使用父组件的数据了,循环组件 ...

  9. 组件通信-父组件为子组件传递数据-静态数据//动态数据 // 数据校验

    组件通信-父组件为子组件传递数据-静态数据 <!DOCTYPE html> <html lang="en"><head><meta cha ...

最新文章

  1. 十分钟上手2022latex安装与入门
  2. python朋友圈为什么这么火-看我如何用Python发一个高逼格的朋友圈
  3. 如何在多版本anaconda python环境下转换spyder?
  4. 2017.10.10 狼和羊的故事 思考记录
  5. IT女性必备——5个方法变身小腰精
  6. 【数组】Triangle
  7. python主程序子程序_python子程序
  8. python学习day02
  9. 《Netty Zookeeper Redis 高并发实战》 图书简介
  10. 面试题 -二元查找树转变成排序的双向链表
  11. 2022年python大数据开发学习路线
  12. [笔记]树的计数 Prufer序列+Cayley公式
  13. Unity 基础 之 实现简单监听晃动(摇一摇)手机设备震动手机设备的事件的功能
  14. 电子不停车收费系统(ETC)专题(5)——最新动态
  15. 论混合软件架构的设计
  16. 52数学能力测评历年真题及答案(五年级)
  17. 下单以及订单处理流程描述
  18. 【Linux篇】第十六篇——生产者消费者模型
  19. vue项目优化及步骤
  20. SQL 注入攻击:简介与原理

热门文章

  1. 已知a类被打包在packagea_2021考研干货:199管理类联考综合逻辑归纳习题(1)
  2. display属性_Numpy知识点(1)讲解实操安装/属性/数组创建/运算
  3. h5优秀控件_H5前端学习的js插件大全,基本包含了大部分的前端最前沿的js插件和库。...
  4. oracle ora 12011,执行oracle中的job报错:ORA-12011:无法执行作业1
  5. 影响索引的mysql函数_mysql索引对排序的影响实例分析
  6. c语言fork()创建线程,操作系统的创建原语是fork()还是creat()?
  7. Latex调整表格与标题表名之间的间距
  8. logstash-input-redis源码解析
  9. Spark初识-弹性分布式数据集RDD
  10. Problem B: 结构体---职工信息结构体