1. 总体想法
- 线程池初始时创建任务队列、线程池,添加并启动子线程,提供接口给用户添加任务到任务队列
- 子线程只管从消息队列中取任务执行,将执行结果异步返回给用户
- 线程池销毁时等待所有子线程退出后才销毁

2. 难点
1. 如何接收用户指定的各种任务
传入线程的都是函数,而函数的不同主要是传入的参数、返回值不同,导致没法使用一个函数指针队列来接受全部类型的函数指针
submitTask
提供用于向任务队列中添加任务的API,由用户调用,参数类型为Task
智能指针- 使用多态 :用户使用派生类指定任务,任务队列元素为指向基类的指针,且基类中定义一个纯虚函数
run()
,让子线程拿到基类指针后运行 - 但是如果用户传入的是临时对象,空间释放后再访问会导致未定义行为,所以要求用户传入shared智能指针,使得主线程出作用域后Task派生类对象依然不会被释放
2. 线程池如何添加任务并将任务队列的任务下发给线程
线程和线程池是两个对象,也就是解决,对象之间如何通信的问题
submitTask
是生产者,用于向任务队列中添加任务threadFunc
是消费者,用于从任务队列中取出任务,注意这个函数是在子线程对象中调用,且需要访问线程池对象中的任务队列- 线程池通过将
threadFunc
传递给子线程,让其执行该函数(消费任务队列),从而实现任务下放(此时子线程在此函数中拥有资源线程池
)
3. 如何实现Any类
- 接受任意类型:构造函数模板
- 存储任意类型:多态 + 类模板
- 返回指定类型:函数模板 + dynamic_cast
4. 用户如何获取任务执行后的结果(异步)
对于任务,主线程将任务到任务队列等待读取结果,子线程从任务队列中取出执行写入结果,读者-写者模型
- 首先要有一个
Result
对象用于接受任意Any
结果 - 子线程要能访问到该对象并写入结果后通知主线程读取,即在
Task
中包含Result
对象指针- 指针赋值也是关键的,
Task
对象是用户提供并调用submitTask传入的,所以需要在这里面创建Result
、将Task
中的Result
指针赋值为此Result
,最后将该Result
对象返回给用户调用地方 Task
和Result
是一一对应 关系,两者有彼此的指针,注意Task
周期比Result
周期短,所以Result
中使用shared_ptr
,要注意避免循环引用Result
中的Task
是为了将Result
赋值给Task
中对应指针
- 指针赋值也是关键的,
- 主线程在读取结果时如果还没有产生,就需要阻塞等待,借助信号量来实现
目前未解决的问题是,Result是栈上对象,如果出作用域就直接析构,但是此时子线程正在写入,就会出现问题:解决办法是变为堆对象,且由智能指针管理,使得写入时不会被析构
5. 线程是如何归还到线程池
不存在归还,子线程一直运行,需要结束时退出循环即可,但是需要从
thread_
中删除自己
- 如果是fixed模式,不需要归还,每个线程创建后就直接启动,死循环读取任务队列
- cached需要动态调整线程池thread_,任务太多创建新线程加入线程池并启动线程,不需要多余线程时需要从线程池中去掉对应的Thread对象并关闭线程
- 这里也存在读者-写者模型,临界资源是
thread_
,主线程和子线程都会对thread_
进行写 - 但是对于
thread_
的操作都是在taskQue_
之下完成,本身就有锁,所以不需要增加多余的锁 - 难点在于:在子线程中去删掉
thread_
对应的线程,thread_
需要使用map
,并将key
传入子线程让其从线程池中删除自己
- 这里也存在读者-写者模型,临界资源是
6. 线程池退出后如何结束所有子线程
线程池是主线程,这里涉及到进程间通信,读者写着模型:
isPoolRunning
以及threads_
threadPool
在析构时改变isPoolRunning
通知子线程,并阻塞等待子线程结束,感知到子线程全部结束- 子线程状态不唯一,可能正在执行任务、可能正在等待任务,但最终都会处于即将等待任务,此时可以读取到线程池要结束了,然后将自己对应的
thread
对象从线程池中清除
7. 优化输入输出
- 上述的输入是借助多态,但是得先定义类,输出是Result,但是目前看来存在多线程的问题
- 目标是
submitTask
接受函数指针和参数,返回一个结果对象,没有多线程问题
submitTask
接受函数,以及可变参--》可变模板- 异步返回值--》
packaged_task<>
、future<>
- 任务队列统一类型--》
lambda
中间件 - 函数返回类型借助传入参数确定--》
auto
、后置返回类型、decltype
3.从数据结构中看线程池
待补充