线程池(基于C++11实现)
目录
本文中涉及的C++11特性 完整AC代码(GitHub)
1.线程池的设计
线程池的基本思想是预先创建一定数量的线程,并将它们放入一个池中。线程池负责管理线程的生命周期,并将任务分配给空闲的线程。当任务到达时,线程池会从池中取出一个空闲线程来执行任务。当任务完成后,线程会返回到池中,等待下一个任务的到来。这种方式可以避免频繁地创建和销毁线程,从而提高系统的性能和资源利用率。
如果要编写一个线程池,需要考虑以下几个关键点:
- 线程池的管理器:负责创建、销毁线程、维护线程状态。
- 任务队列:用于存储执行的任务,任务通常以函数对象(如
std::function)的形式存储。 - 工作线程:线程池中的实际线程,它们从任务队列中取出任务并执行。
- 同步机制:为了确保线程安全,需要使用互斥锁、条件变量等同步机制来保护共享资源。
在设计线程池时,还需要考虑以下几个方面:
- 线程池的大小管理:
- 固定大小:线程池中线程的数量是固定的,不会动态增加或减少,适用于负载相对稳定的场景。
- 动态调整:格局任务负载动态调整线程池的大小。适用于负载变化较大的场景。
- 任务队列管理:
- FIFO队列:最常用的任务队列实现方式,按照任务的提交顺序执行任务。
- 优先级队列:根据任务的优先级执行任务,适用于需要按优先级处理任务的场景。
- 线程安全:
- 互斥锁:用于保护共享资源(如任务队列)的访问。
- 条件变量:用于线程之间的通信,如通知空闲线程有新的任务到来。
- 原子变量:对原子变量的操作都是原子操作,它是线程安全的。
- 任务执行与错误处理:
- 任务执行过程中,如果发生异常,需要正确处理异常,避免程序崩溃。

2.使用C++11实现线程池
2.1 头文件
#include <thread>
#include <vector>
#include <queue>
#include <map>
#include <atomic>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <future>
#include <memory>
#include <iostream>
/*
构成:
1.管理者线程: 子线程 1个
控制线程池的创建、销毁、线程数量的调整
2.工作线程: 子线程 n个
从任务队列中取出任务,并处理
任务队列为空,被阻塞(被条件变量)
线程同步(互斥锁)
当前数量,空闲的线程数量
最小、最大线程数量
3.任务队列: stl->queue
互斥锁
条件变量
4.线程池开关 bool
*/
class ThreadPool
{
public:
ThreadPool(int min = 2, int max = std::thread::hardware_concurrency());
~ThreadPool();
void addTask(std::function<void(void)> task); // 添加任务 -> 任务队列
private:
void manager(void);
void worker(void);
private:
std::queue<std::function<void(void)>> m_tasks;
std::mutex m_queueMutex;
std::mutex m_idsMutex;
std::conditon_variable m_condition;
std::atomic<bool> m_stop;
std::thread *m_manager;
std::map<std::thread::id, std::thread> m_workers;
std::vector<std::thread::id> m_ids;
std::atomic<int> m_minThread;
std::atomic<int> m_maxThread;
std::atomic<int> m_curThread;
std::atomic<int> m_idleThread;
std::atomic<int> m_exitThread;
};
相关函数介绍:
- 构造函数:初始化线程池,并创建管理器线程、工作线程。
- 析构函数:销毁线程池,等待所有工作线程退出;设置
m_stop标志位true,通知所有工作线程退出,并释放资源。 std::thread::hardware_concurrency()是C++标准库中std::thread类的一个静态成员函数,用于获取系统支持的并发线程数。manager():管理者线程的任务函数。worker():工作线程的任务函数。addTask(std::function<void(void)> task)将任务添加到任务队列中,并通知工作线程有新任务到来。
2.2 线程池实现
#include "ThreadPool.h"
ThreadPool::ThreadPool(int min, int max) : m_maxThread(max), m_minThread(min), m_stop(false), m_curThread(min), m_idleThread(min)
{
m_manager = new std::thread(&ThreadPool::manager, this);
for(int i = 0; i < min; ++i) {
std::thread t(&ThreadPool::worker, this);
m_workers.insert(std::maker_pair(t.get_id(), std::move(t)));
}
}
ThreadPool::~ThreadPool()
{
m_stop = true;
m_condition.notify_all();
if((*m_manager).joinable())
{
(*m_manager).join();
delete ma_manager;
m_manager = nullptr;
}
for(auto &it : m_workers)
{
std::thread &t = it.second;
if (t.joinable())
{
std::cout << "***************线程 " << t.get_id() << " 将要退出了 ...\n";
t.join();
}
}
m_workers.clear();
}
void ThreadPool::addTask(std::function<void(void)> task)
{
{
std::lock_guard<std::mutex> lock(m_queueMutex);
m_tasks.push(task);
}
m_condition.notify_one();
}
void ThreadPool::manager()
{
while(!m_stop.load())
{
std::this_thread::sleep_for(std::chrono::seconds(1));
int idle = m_idleThread.load();
int cur = m_curThread.load();
if(idle > cur / 2 && cur > m_minThread)
{
m_exitThread = cur - m_minThread;
m_condition.notify_all();
std::lock_guard<std::mutex> lock(m_idsMutex);
for(auto id : m_ids)
{
auto it = m_workers.find(id);
if(it != m_workers.end())
{
std::cout << "----------线程 " << (*it).first << " 被销毁了 ...\n";
(*it)->second.join();
m_workers.erase(it);
}
}
m_ids.clear();
} else if(idle == 0 && cur < m_maxThread) {
std::thread t(&ThreadPool::worker, this);
m_workers.insert(std::make_pair(t.get_id(), std::move(t)));
m_curThread++;
m_idleThread++;
}
}
}
coid ThreadPool::worker()
{
while(!m_stop.load())
{
std::function<void(void)> task = nullptr;
{
std::unique_lock<std::mutex> locker(m_queueMutex);
while (m_tasks.empty() && !m_stop.load())
{
m_condition.wait(locker);
if(m_exitThread.load() > 0)
{
m_curThread--;
m_idleThread--;
m_exitThread--;
std::cout << "----------线程退出了,ID: " << std::this_thread::get_id() << std::endl;
std::lock_guard<std::mutex> lock(m_idsMutex);
m_ids.emplace_back(std::this_thread::get_id());
return;
}
}
if(!m_tasks.empty())
{
std::cout << "取出了一个任务..." << std::endl;
task = move(m_tasks.front());
m_tasks.pop();
}
}
if(task)
{
m_idleThread--;
task();
m_idleThread++;
}
}
}
使用C++11中提供的类,整体代码比C语言风格API代码更简洁,可读性更强。
void worker()工作线程的任务函数- 工作的线程在该函数内部通过
while (!m_stop.load())循环重复的进行从任务队列取数据 -> 处理数据的操作。 - 如果线程池没关闭并且任务队列为空,工作线程被条件变量阻塞。
- 通过
addTask往任务队列中添加新任务之后,可以唤醒被条件变量阻塞的线程。
- 工作的线程在该函数内部通过
void manager()管理线程的任务函数- 通过睡眠的方式,每3秒检查一次线程池中的线程数量,如果空闲线程数量大于总线程数量的一半,并且当前线程数量大于最小线程数量,则销毁多余的线程。
- 如果空闲线程数量为0,并且当前线程数量小于最大线程数量,则创建新的工作线程。
void addTask(std::function<void(void)> task):- 添加新的任务到任务队列中,并
m_condition.notify_one唤醒一个被阻塞工作线程。
- 添加新的任务到任务队列中,并
测试代码:
void calculate(int x, int y)
{
int z = x + y;
std::cout << "z = " << z << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
}
int main()
{
ThreadPool pool(2, 24);
for(int i = 0; i < 10; ++i)
{
auto obj = std::bind(calculate, i, i * 2);
pool.addTask(obj);
}
std::cout << "按回车退出..." << std::endl;
getchar();
return 0;
}
- 关于main() 函数中的测试代码
- 通过添加任务函数
addTask可以得知任务函数返回值类型是void并且无参,但是测试程序中添加的任务函数calculate有两个参数,很显然和任务函数的参数是类型是不匹配的,因此需要使用std::bind将calculate函数和参数绑定起来。
- 通过添加任务函数
auto obj = std::bind(calculate, i, i * 2);
这样得到的可调用对象就是无参的。通过这种方式表面看起来只能存储无参函数的任务队列,就可以存储带任意参数的有参函数了。
getchar()是一个阻塞函数,当程序执行到getchar()时,程序会等待用户输入,用户输入之后,程序才会继续执行,主线程执行完毕,主进程也就退出了。

浙公网安备 33010602011771号