前言
这是我们操作系统专业课的一次编程作业,作为菜鸡的我搜索了大量的线程池博客,但不是代 码太秀,比较难懂,就是实现的线程池功能太过强大,不适合入门,或是讲解重点并没解决我 的问题,而不同博客使用的头文件又不同;很难有只需一篇就能让菜鸡搞懂的博客。
所以我打算以我从零开始理解的角度完成这篇 实验报告 博客,能让最菜的菜鸡也能看懂并手 动实现一个最简单的线程池。
但要做到这一点,需要有一点点前提条件:
- 只需要有简单的c++面向对象编程的能力。
- 会使用win自带的 “windows.h” 头文件进行多线程编程。
- 对线程有基本的理解。
而我们最终能得到的线程池拥有的功能有:
- 实现线程池最基本的功能能:线程重复利用,少量线程完成大量工作(线程数<任务 数)。
- 可自己设计任务函数,而不是简单的模拟。(同GreatThread() 传入参数的方法类似)
- 可调节线程池的线程数,空闲线程等待时间等。
- 可随时添加新任务,添加结束后可调用等待函数等待线程完成所有工作。
- 不能直接接收返回值,但返回值可利用传入的参数指针传回。
基本概念
-
为啥要有线程池
一般的多线程编程,往往是一个线程对应一个 ‘任务’ 。但由于线程的建立于销毁也会造成一部分开销,所以当任务过多的时候,若还是一个任务对应一个线程,线程的反复建立与销毁,会造成大量的的资源浪费。 -
什么是任务
这一点是大部分博客都没有提及的,以我的理解,这里的任务所指的是你打算在线程函数里面完成的工作,比如打印 “hello word” 或者做一些计算之类的。
而大部分任务往往都能封装进一个函数,所以这里的任务往往就是指一个有明确目的函数(相同函数传入不同参数也能看成不同的任务) -
什么是线程池
如果我们重复利用几个线程,去完成比线程数更多的任务,那不就避免了线程建立和销毁造成的开销么。
一句话说:线程池就是一种利用少量线程解决大量任务(线程数 < 任务数)的工具。
而高级的线程池会从线程的数量,线程的活跃时间,任务的存取策略等角度进行优化,以达到更高效目的。 -
如何实现
我们可以把所有的任务放在一个队列内,并创建一定数量的线程。线程空闲时,可以自动从任务队列中取出任务,任务完成后,又进入空闲,并尝试取出下一个任务。而这,就是最简单的线程池了。
代码实现
“任务”的封装
综上所述,我们需要实现的任务需要有这样的特性:
- 创建任务和运行任务可以不同步(不同于简单的调用函数)。
- 因为要放入队列,所以需要有具体的数据结构。
- 需要固定任务的格式(总不能让用户随便传入啥类型的函数都行吧,得规范一下)
解决方案:
首先,统一任务函数的格式:”参数为 void 指针,返回值为void(没有返回值)“。
这样的话,利用void指针可以指向任何数据的特性,任务函数可以传入任意类型的参数了,也可以利用指针传回返回值。
// 声明fp_return_void类型
typedef void(*fp_return_void)(void *paramter);
见:函数指针的用法
其次,将函数指针和参数指针分开来, 并进行封装,想运行函数的时候,直接调用函数指针并把参数传入即可(见run()成员函数)
class Task
{
private:
// 函数指针和参数指针
fp_return_void fp;
void *paramter;
// 构造函数,需传入函数指针和参数指针
Task(fp_return_void fp, void *param) :
fp(fp), paramter(param) {
}
// 运行任务
void run()
{
fp(paramter);
}
public:// 声明为线程池的友元类
friend class KyThreadPool;
};
Task使用方法:
// 声明自己的任务函数
void FuncName(void *lpParamter)
{
int *pmd = (int *)lpParamter;// 假设需要传入int指针,(int *)表示强制类型转换,相当于告诉函数void指针指向的是int类型。
cout << "task " << pmd[0] << " is working!\n";
}
//声明参数
int x = 1;
int *p = &x;
// 声明一个任务
Task onetask(FuncName, p);
onetask.run(); // 运行任务
线程池框架构建
根据线程池的概念,线程池的主体必要两个东西:
- 线程函数,有任务的时候做任务,没任务的时候等任务,等久了就自动结束。
- 任务队列,用于存放任务,供线程函数提取任务用。(见:STL队列用法)
还需要一些池子的参数:
- 核心线程数/最大线程数(为了简便,假设核心线程数 = 最大线程数)
- 空闲线程等待时间,空闲线程也会占用cpu,所以不能让空闲线程活太久
- 现存线程数量,活跃线程数量,线程结束标志等,用于管理线程
以及几个供给用户使用的函数:
- 等待线程池函数,线程停止接收任务,等待剩余线程完成任务。
- 重启线程池函数,使线程池重新工作
- 添加任务函数,用于增加一个任务到队列中。

本文介绍如何使用C++和Windows.h构建简单线程池,包括任务封装、线程池框架构建及成员函数实现。适用于初学者,涵盖线程重复利用、自定义任务函数、线程数调节等功能。
&spm=1001.2101.3001.5002&articleId=89437073&d=1&t=3&u=d3c85d284e434c9f89a8600c28f068d8)
4206

被折叠的 条评论
为什么被折叠?



