目录
1.认识线程池
什么是线程池
线程池(Thread Pool)是线程的一种使用方式。它通过预先创建一组线程并管理它们的生命周期,来提高多线程应用程序的性能和资源利用率。线程池的核心思想是减少在创建和销毁线程时所产生的开销。
线程池的优点
-
减少开销:线程的创建和销毁是有开销的,线程池通过复用线程减少了这种开销。
-
提高响应速度:当任务到达时,线程池中已经有现成的线程可以立即执行任务,而不需要等待线程创建。
-
提高线程的可控性:线程池可以统一管理线程的生命周期、数量和优先级,使得多线程编程更加可控。
-
防止资源耗尽:通过限制线程的数量,线程池可以防止系统因创建过多线程而耗尽资源。
线程池的使用场景
-
Web 服务器处理请求:Web 服务器需要同时处理大量客户端请求,每个请求通常是一个短期任务(如 HTTP 请求)。使用线程池可以避免为每个请求创建和销毁线程的开销,提高服务器的响应速度和资源利用率。
-
**数据库连接池:**数据库操作通常是 I/O 密集型任务,频繁创建和销毁数据库连接会消耗大量资源。通过线程池管理数据库连接,可以复用连接,减少创建和销毁连接的开销。
-
**异步任务处理:**某些任务不需要立即执行,可以异步处理(如日志记录、邮件发送、消息推送等)。将任务提交到线程池中异步执行,避免阻塞主线程。
-
批量数据处理:需要对大量数据进行处理(如文件解析、数据清洗、批量计算等),这些任务可以并行执行。将数据分片后提交到线程池中并行处理,提高处理效率。
关于线程池的应用还有很多,笔者就不一 一赘述了。
2.线程池的实现
线程池的组成
线程池内部主要有一个任务队列 用于存储任务,还有一批线程 从任务队列中获取任务并执行。任务队列中的任务通过一个控制线程 传递进来,并且控制线程还要管理好线程池的生命周期,但是控制线程并不属于线程池。

线程池之间的关系
线程池代码中的角色有主线程 和线程池中的从线程 ,主线程只有一个,从线程有多个,主线程往任务队列中放数据,从线程从任务队列中获取数据;此时的**任务队列就是主线程和从线程之间的共享资源(也是临界资源)。**因此,我们要讨论线程池中各角色之间的关系,只有讨论清楚关系了才能写出正确的并且健壮的代码。
- 主线程和主线程之间 ------ 主线程只有一个,不讨论。
- 主线程和从线程之间 ------ 互斥和同步:主线程在放数据的时候,从线程不能来获取数据,否则获取到的数据可能不完整,他们之间需要互斥的访问临界资源。而主线程生产数据之后,需要通知从线程来拿,任务队列中没有数据的时候,从线程需要进行排队等待,因此,他们之间具有同步关系。
- 从线程和从线程之间 ------ 互斥和同步:一个线程在拿数据,另一个线程就不能拿,否则可能拿到一样的数据,因此,他们之间需要互斥。为了避免某些进程竞争锁的能力太强导致的线程饥饿问题,当任务队列为空的时候,多个线程需要在条件变量下排队等待,因此,他们之间需要保持同步。
线程池的实现代码
我们设计的代码需要一个任务类型、一个线程池类型、一个主程序即可。
任务类型
我们的任务类型只是简单的做一下加法即可,有想法的读者可以设计自己的任务类型。
#include <iostream>
#include <string>
class Task
{
public:
Task() {}
Task(int num1, int num2) : _num1(num1), _num2(num2), _result(0)
{
}
void Excute()
{
_result = _num1 + _num2;
}
void ResultToString() // 打印计算结果
{
std::cout << "Excute Task: " << std::to_string(_num1) + "+" + std::to_string(_num2) + "=" + std::to_string(_result) << std::endl;
}
std::string DebugToString() // 打印生产的任务
{
return std::to_string(_num1) + "+" + std::to_string(_num2) + "=?";
}
~Task()
{}
private:
int _num1;
int _num2;
int _result;
};
线程池类型
#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include "task.hpp"
#define THREAD_NUM 5
template<class T>
class ThreadPool
{
private:
int _thread_num; // 线程个数
std::queue<T*> _task_queue; // 任务队列
pthread_mutex_t _lock; // 互斥量,用于保护临界资源 ------ 任务队列
pthread_cond_t _cond; // 条件变量,维护线程池中的线程按照
private:
void LockQueue()
{
pthread_mutex_lock(&_lock);
}
void UnLockQueue()
{
pthread_mutex_unlock(&_lock);
}
void WakeUpOne()
{
pthread_cond_signal(&_cond);
}
void ThreadWait()
{
pthread_cond_wait(&_cond, &_lock);
}
bool IsEmptyQueue()
{
return _task_queue.empty();
}
static void *handler(void *arg)
{
ThreadPool* tp_ptr = (ThreadPool*)arg;
while(true)
{
// 申请锁
tp_ptr->LockQueue();
// 判断任务队列是否为空 如果为空就让线程去等待
while(tp_ptr->IsEmptyQueue())
{
tp_ptr->ThreadWait();
}
// 代码走到这里说明任务队列不为空
Task *task; // 输出型参数,用于获取任务
tp_ptr->GetTask(&task); // 获取任务
tp_ptr->UnLockQueue(); // 释放锁
task->Excute(); // 执行任务
task->ResultToString(); // 打印执行结果
delete task; // 任务是malloc的,使用完需要delete
}
return NULL;
}
public:
ThreadPool(int num = THREAD_NUM)
:_thread_num(num)
{
// 构造时初始化互斥量和条件变量
pthread_mutex_init(&_lock, NULL);
pthread_cond_init(&_cond, NULL);
}
~ThreadPool()
{
// 析构时销毁互斥量和条件变量
pthread_mutex_destroy(&_lock);
pthread_cond_destroy(&_cond);
}
bool InitThreadPool() {
pthread_t tid;
// 创建一批线程
for (int i = 0; i < _thread_num; i++)
{
int ret = pthread_create(&tid, NULL, handler, this);
if (ret != 0)
{
std::cout<<"create pool thread error\n";
return false;
}
}
return true;
}
void PushTask(Task *task)
{
// 加锁
LockQueue();
// 向任务队列中放数据
_task_queue.push(task);
// 通知线程池中正在等待的线程来获取任务
WakeUpOne();
// 解锁
UnLockQueue();
}
void GetTask(Task **task)
{
*task = _task_queue.front();
_task_queue.pop();
}
};
主程序
#include "ThreadPool.hpp"
int main()
{
// 定义线程池
ThreadPool<Task> thread_pool;
// 初始化线程池
if( thread_pool.InitThreadPool())
{
std::cout << "Create threadpool success" << std::endl;
}
srand(time(NULL));
while(true) {
// 主线程创建任务
int num1 = rand()%1000;
int num2 = rand()%1000;
Task* task = new Task(num1, num2);
// 打印创建的任务
std::cout << "Create Task: " << task->DebugToString() << std::endl;
// 向任务队列中放入任务
thread_pool.PushTask(task);
// 每个1秒创建一个线程
sleep(1);
}
return 0;
}
运行结果:

- 可以看到主线程创建的任务被线程池中的线程获取并执行了。