C++多线程快速入门(五)简单线程池设计
目录
- 设计思路
- 主线程运行逻辑
- task以及taskpool设计
- 详细流程讲解
- 完整代码
- 打印结果
- 往期回顾
设计思路
线程池实际上就是一组线程,当我们需要异步执行一些任务时,经常要通过OS频繁创建和销毁线程,不如直接创建一组在程序生命周期内不会退出的线程。
当有任务需要被执行时,线程可以自动地去拿任务并执行,在没有任务时,线程处于阻塞或者睡眠状态。
我们选择队列存放任务,main函数中的thread作为任务的生产者,从队列尾部插入任务。线程池中的线程作为消费者,从队列头部获取任务。
复杂考虑的话,当线程池的线程在处理任务的过程中也产生了关联任务,那么这个线程也是消费者。
队列也可以设计得更加具有实用性,例如可以根据任务的优先级设计多个队列,然后线程根据优先级获取线程(也就是先查询高优先级的队列,为空再去查询低优先级的队列)。
综上,我们的设计中会有多个线程访问任务队列,所以我们要解决线程池创建、向队列投放任务、从队列中获取任务的线程互斥性。
同时线程池的清理、退出线程池中的工作线程、清理任务队列,也是需要考虑的。
并且,为了能够详细获知多线程获取多任务的流程,我们需要对taskID和threadID进行输出打印,std::cout
并不是线程安全的,所以我们也要实现互斥地cout。
这里我们统一使用互斥量std::mutex
+lock来实现互斥性
主线程运行逻辑
task以及taskpool设计
Task:
TaskPool:
内部变量:
详细流程讲解
1、创建线程池对象时,调用构造函数TaskPool(),并初始化布尔类型的标志m_bRunning为false,表示此时线程池对象中的线程不工作
2、调用线程池对象的初始化函数init()
,这里默认的线程数为5。
- m_bRunning置为true,表示线程池对象中的线程应该开始运行了
- 然后通过for循环,每次构造一个新的线程对象,并且绑定一个线程函数
TaskPool::threadFunc
,创建之后线程就开始工作了。然后打印出当前的线程id,注意此时需要上锁,保证cout输出正常。然后将线程对象送入m_threads
数组
3、接下来看看构造出来的每个线程在干啥:
while(true)
{{上锁,访问任务队列// 注意由于队列本身是个多线程共享资源,所以对于队列取元素以及状态判断都是要先加锁再操作队列为空则一直循环 // 为什么要用循环判断呢?// 这是因为wait()从阻塞到返回,不一定就是由于notify_one()函数造成的,还有可能由于系统的不确定原因唤醒(可能和条件变量的实现机制有关),这个的时机和频率都是不确定的,被称作伪唤醒,如果在错误的时候被唤醒了,执行后面的语句就会错误,所以需要再次判断队列是否为空,如果还是为空,就继续wait()阻塞。{如果m_bRunning为false,说明此时应该中止线程操作,所以需要连着跳出两个循环,所以直接用goto label吧否则就一直等待在这儿,wait()可以让线程陷入休眠状态,在消费者生产者模型中,如果生产者发现队列中没有东西,就可以让自己休眠.}此时 获取队头元素,并将队头元素出队}// 为了减少锁的粒度,接下来的操作不需要加锁了,因为已经拿到了队列中的元素执行Task对象的doIt()方法,也就是打印任务id和线程id
}
label :
打印当前线程id
4、for循环,创建10个Task对象,然后调用addTask方法,将task送入线程池中的任务队列。显然push操作是互斥的,所以需要先上锁。然后打印任务id和线程id,最后通过条件变量的notify_one方法,通知一个挂起的线程去消费队列里面的任务。
5、等待一段时间
6、调用线程池对象的stop方法,先设置m_bRunning标志为false,然后通过条件变量的notify_all方法,通知挂起的或者正在运行的所有线程,结束线程函数运行。然后等待所有线程join之后,退出。
7、跳出主线程,开始调用TaskPool对象的析构函数,也就是执行removeAllTasks方法,也就是将任务队列里面的存的task指针进行reset,也就是减引用计数,shared_ptr指针如果引用计数减为0,会自动调用析构函数。为了线程安全,我们同样需要对这块代码进行加锁。
完整代码
c++实现简单线程池代码
打印结果
Init a thread, id: 2
Init a thread, id: 3
Init a thread, id: 4
Init a thread, id: 5
Init a thread, id: 6
add a Task, id: 0, thread id is: 1
add a Task, id: 1, thread id is: 1
add a Task, id: 2, thread id is: 1
handle a task ,TaskID is: 1, thradID is:3
a task destructed , TaskID is: 1, thradID is:3
handle a task ,TaskID is: 2, thradID is:4
a task destructed , TaskID is: 2, thradID is:4
handle a task ,TaskID is: 0, thradID is:2
a task destructed , TaskID is: 0, thradID is:2
add a Task, id: 3, thread id is: 1
handle a task ,TaskID is: 3, thradID is:3
a task destructed , TaskID is: 3, thradID is:3
handle a task ,TaskID is: 4, thradID is:5
add a Task, id: 4, thread id is: 1
a task destructed , TaskID is: 4, thradID is:1
add a Task, id: 5, thread id is: 1
handle a task ,TaskID is: 5, thradID is:4
a task destructed , TaskID is: 5, thradID is:4
add a Task, id: 6, thread id is: 1
handle a task ,TaskID is: 6, thradID is:4
a task destructed , TaskID is: 6, thradID is:4
handle a task ,TaskID is: 7, thradID is:2
add a Task, id: 7, thread id is: 1
a task destructed , TaskID is: 7, thradID is:1
add a Task, id: 8, thread id is: 1
add a Task, id: 9, thread id is: 1
handle a task ,TaskID is: 9, thradID is:6
a task destructed , TaskID is: 9, thradID is:6
handle a task ,TaskID is: 8, thradID is:5
a task destructed , TaskID is: 8, thradID is:5
exit thread , threadID:3
exit thread , threadID:4
exit thread , threadID:2
exit thread , threadID:5
exit thread , threadID:6Process finished with exit code 0
往期回顾
C++多线程快速入门(四)shared_mutex以及读写锁应用
C++多线程快速入门(三):生产者消费者模型与条件变量使用
C++多线程快速入门(二)共享数据同步以及数据竞争
C++多线程快速入门(一):基本&常用操作
C++多线程快速入门(五)简单线程池设计相关推荐
- Java 高级 --- 多线程快速入门
这世上有三样东西是别人抢不走的:一是吃进胃里的食物,二是藏在心中的梦想,三是读进大脑的书 多线程快速入门 1.线程与进程区别 每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.线程是一组 ...
- Linux多线程实践(9) --简单线程池的设计与实现
线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收.所以 ...
- Java 多线程快速入门(面试概念解答一)
Java 多线程快速入门 什么是进程,什么是线程,什么是多线程? 创建多线程有哪些方式? 启动线程是使用调用start方法还是run方法? 获取线程对象以及名称 守护线程 使用setDaemon(tr ...
- Java多线程(十一)之线程池深入分析(上)
线程池是并发包里面很重要的一部分,在实际情况中也是使用很多的一个重要组件. 下图描述的是线程池API的一部分.广义上的完整线程池可能还包括Thread/Runnable.Timer/TimerTask ...
- Java多线程学习(八)线程池与Executor 框架
历史优质文章推荐: Java并发编程指南专栏 分布式系统的经典基础理论 可能是最漂亮的Spring事务管理详解 面试中关于Java虚拟机(jvm)的问题看这篇就够了 目录: [TOC] 本节思维导图: ...
- AS3多线程快速入门(三):NAPE物理引擎+Starling
原文:http://blog.domlib.com/articles/345 [更新]Adobe在11.4正式发布的最后一刻移除了ByteArray.shareable功能的支持,推迟到11.5版本再 ...
- AS3多线程快速入门(三):NAPE物理引擎+Starling[译]
原文链接:http://esdot.ca/site/2012/intro-to-as3-workers-part-3-nape-physics-starling [更新]Adobe在11.4正式发布的 ...
- java线程池详解及五种线程池方法详解
基础知识 Executors创建线程池 Java中创建线程池很简单,只需要调用Executors中相应的便捷方法即可,比如Executors.newFixedThreadPool(int nThrea ...
- 多线程进阶(五)--线程间的通信
多线程基础概念:多线程入门(一) 多线程基础实现:多线程入门(二) 多线程管理:多线程基础(三) 线程间的状态转换:多线程基础(四) 本篇我们就简单的介绍下,线程间的通信: 多线程进阶(五)--线程间 ...
最新文章
- 获取子iframe的属性
- PLSQL DEVELOPER 使用的一些技巧【转】
- 团队-石头剪刀布-模块测试过程
- Java IO流之随机读写流RandomAccessFile
- c语言的四个函数,C语言学习之动态内存分配的四个函数
- 提交留言HTML模板代码
- OneStep 移植
- 维特WT931 姿态角度传感器(陀螺仪)使用记录
- linux中搭建git与链接github的用法
- MCP2515 (2)
- 网络营销推广文案如何量化工作和管理
- 【实例】用PHP制作一个简单的日历
- 年龄计算机在线计算适合你的对象,抖音年龄计算器恋爱对象生日,年龄计算器适合你的恋爱对象...
- 原来自动驾驶离不开OpenStack
- Linux窗口和Win命令窗口查看mysql bit类型的值
- 服务器网络适配器多路传送器协议,win8系统开启Microsoft网络适配器多路传送协议的具体方法...
- 一文搞清电子认证相关概念:CA、证书、PKI、CSR、SSL、TSL、CRT、CER、PEM、RSA等
- 第八章 自我反省的功夫
- 面试官反感的求职者(下)
- qs.js库 使用方法