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.从数据结构中看线程池
待补充