线程池在平时工作开发中是用的比较多的,例如后端开发中和数据库的交互,单线程在很多场景下受限于数据库连接性能限制,速度不理想,此时采用线程池+连接池,就可以更高性能的和数据库进行交互。
C++标准库轮子很少,线程池一般都要自己实现,而网上的一些线程池实现或多或少都有各种问题,经不起企业线上的考验。
本篇文章提供一个方便易用的C++跨平台线程池实现,支持Windows和Linux,包含一个.h和.cpp文件,并且该实现是经受过企业线上高并发考验的,绝对没有暗坑,现分享给大家,给C++的轮子添上一块砖瓦。
废话不多说,直接上.h和.cpp文件以及一个测试例子,如果积分有富余的,可以直接下载vs工程例子: https://download.csdn.net/download/ge_enli/87350160,当然,本文里的代码也都是完整的,不用担心。
ThreadPool.h
/*************************************************
* @brief 跨平台线程池
* @author 49042765@qq.com
* @date 2020-08-25
*************************************************/
#pragma once
#include <list>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <future>
class ThreadPool;
// 任务类,使用的时候继承该类,重写run函数就可以了。
class Task
{
private:
int m_priority;
public:
enum PRIORITY
{
MIN = 1,
NORMAL = 15,
MAX = 25
};
Task() {};
Task(PRIORITY priority) : m_priority{ priority } {}
virtual ~Task() {};
void setPriority(PRIORITY priority)
{
m_priority = priority;
}
virtual void run() = 0;
};
// 工作线程
/* 这个类还可以单独使用,用于开启多线程(实线多线程面向对象化)。
有两种使用方法。方法一:继承该类,直接重写run()函数。
方法二:在另一个类中包含该类的引用或指针,分配任务。该线程池的做法就是如此。
其中方法二如果任务执行完了,在没有任务的情况下该线程会阻塞。如果设置了任务
则该线程会唤醒,继续执行。如果已经设置了任务但是该任务还没有执行则设置任务
是失败的。需要结合isExecuting()使用,该函数可以告知当前想成是否在执行任务。
如果想要结束线程则调用stop()函数。该函数会等待线程中的任务执行完毕后,退出线程。
另外,该类是线程安全的,对内部变量的访问都是加了线程同步的。
*/
class WorkThread
{
std::thread m_thread; // 工作线程
Task * m_task; // 任务指针
std::mutex m_mutexThread; // 关于任务是否执行的互斥量
std::mutex m_mutexCondition;// 条件互斥量
std::mutex m_mutexTask; // 关于分配任务的互斥量
std::condition_variable m_condition; // 任务条件变量
std::atomic<bool> m_bRunning; // 是否运行的标志位
bool m_bStop;
protected:
virtual void run(); // 线程函数
public:
WorkThread();
~WorkThread();
WorkThread(const WorkThread & thread) = delete;
WorkThread(const WorkThread && thread) = delete;
WorkThread & operator=(const WorkThread & thread) = delete;
WorkThread & operator=(const WorkThread && thread) = delete;
bool assign(Task *task); // 分配任务
std::thread::id getThreadID(); // 获取线程ID
void stop(); // 停止线程运行,其实就是结束线程运行
void notify(); // 通知阻塞线程
void notify_all(); // 通知所有的阻塞线程
bool isExecuting(); // 是否在执行任务
};
// 空闲线程列表
// 该类也是线程安全的,所以在使用的过程中没必要给他加锁
class LeisureThreadList
{
std::list<WorkThread *> m_threadList; // 线程列表
std::mutex m_mutexThread; // 线程列表访问互斥量
void assign(const size_t counts); // 创建线程
public:
LeisureThreadList(const size_t counts);
~LeisureThreadList();
void push(WorkThread * thread); // 添加线程
WorkThread * top(); // 返回第一个线程指针
void pop(); // 删除第一个线程
size_t size(); // 返回线程个数
void stop(); // 停止运行
bool isExecuting(); // 是否有线程在执行任务
};
// 线程池
// 该类是线程安全的
/*
该类运行的时候只要调用addTask()添加任务就可以了,任务会自动运行。
如果要线程池退出时必须调用一下exit()函数。
线程池可以暂定执行,只需调用stop()就可以了,要重新开始运行则需调用start()就可以了。
*/
class ThreadPool
{
std::thread m_thread; // 线程池的任务分配线程
LeisureThreadList m_leisureThreadList; // 线程列表
std::queue<Task *, std::list<Task *>> m_taskList; // 任务队列
std::atomic<bool> m_bRunning; // 是否运行
std::atomic<bool> m_bEnd; // 是否结束运行
std::atomic<size_t> m_threadCounts; // 线程总数
std::condition_variable m_condition_task; // 任务条件
std::condition_variable m_condition_thread; // 线程列表条件
std::condition_variable m_condition_running;// 运行条件变量
std::mutex m_runningMutex; // 运行控制变量
std::mutex m_mutexThread; // 空闲互斥量
std::mutex m_taskMutex; // 访问任务列表互斥量
bool m_bExit; // 是否退出标志位
void msleep(unsigned long timems);
void run(); // 线程池主线程函数
public:
ThreadPool(const size_t counts);
~ThreadPool();
size_t threadCounts();
bool isRunning(); // 线程是否正在运行任务
void addTask(Task *task); // 添加任务
// 线程池开始调度任务,线程池创建后不用调用该函数。该函数需要和stop()
// 配合使用。
void start(); // 线程池开始执行
// 线程池暂停任务调度,但是不影响任务添加。想要开始任务调度,则调用
// start()函数
void stop(); // 线程池暂停运行
// 该函数会在线程池中的所有任务都分派出去后结束线程池的线程运行,
// 同样的线程列表中的线程会在自己的任务执行完后,然后再退出。
void exit(); // 线程池退出
};
ThreadPool.cpp
#include "ThreadPool.h"
#include <iostream>
#ifndef _WIN32
#include <sys/prctl.h>
#include <time.h>
#else
#include <windows.h>
#endif
// 工作线程
WorkThread::WorkThread() : m_task{ nullptr }, m_bStop{ false }
{
m_bRunning.store(true); // 意味着该对象创建后线程就运行起来了
// 初始化执行线程
m_thread = std::thread(&WorkThread::run, this);
}
WorkThread::~WorkThread()
{
if (!m_bStop)
stop();
if (m_thread.joinable())
m_thread.join();
}
bool WorkThread::assign(Task *task)
{
m_mutexTask.lock();
if (nullptr != m_task)
{
m_mutexTask.unlock();
return false;
}
m_task = task;
m_mutexTask.unlock();
// 通知线程,然后唤醒线程执行
m_condition.notify_one();
return true;
}
void WorkThread::run()
{
#ifndef _WIN32
prctl(PR_SET_NAME, "task_work", 0, 0, 0);
#endif
while (true)
{
if (!m_bRunning.load())
{
m_mutexTask.lock();
if (nullptr == m_task)
{
m_mutexTask.unlock();
break;
}
m_mutexTask.unlock();
}
Task * task = nullptr;
// 等待任务,如果没有任务并且线程也没有退出的话线程会阻塞
{
std::unique_lock<std::mutex> lock(m_mutexTask);
// 等待信号
m_condition.wait(lock,
[this]() {
return !((nullptr == m_task) && this->m_bRunning.load());
});
task = m_task;
m_task = nullptr;
if (nullptr == task)
{
continue;
}
}
task->run();// 执行任务
delete task;// 释放task内存
task = nullptr;
}
}
std::thread::id WorkThread::getThreadID()
{
return std::this_thread::get_id();
}
void WorkThread::stop()
{
m_bRunning.store(false);//设置停止标志位
m_mutexThread.lock();
if (m_thread.joinable())
{
// 这里一定要先设置标志位后,然后通知线程,唤醒
// 因为有时候这时线程是阻塞的,所以需要通知,然后join
// 否则线程可能不会退出,会一直阻塞
m_condition.notify_all();
m_thread.join();
}
m_mutexThread.unlock();
m_bStop = true;
}
void WorkThread::notify()
{
m_mutexCondition.lock();
m_condition.notify_one();
m_mutexCondition.unlock();
}
void WorkThread::notify_all()
{
m_mutexCondition.lock();
m_condition.notify_all();
m_mutexCondition.unlock();
}
bool WorkThread::isExecuting()
{
// 这里使用m_task来判断是否在执行任务,这样即使任务在执行
// 但是m_task已经被设置位nullptr了,可以再次添加任务。如果
// 后面再次添加任务的话,就不能再添加了。这样也不会丢失任务。
bool ret;
m_mutexTask.lock();
ret = (nullptr == m_task);
m_mutexTask.unlock();
return !ret;
}
LeisureThreadList::LeisureThreadList(const size_t counts)
{
assign(counts);// 创建多个线程
}
LeisureThreadList::~LeisureThreadList()
{
// 删除线程列表中的所有线程
// 这里是析构函数,不用加锁
while (!m_threadList.empty())
{
WorkThread * temp = m_threadList.front();
m_threadList.pop_front();
delete temp;
}
}
void LeisureThreadList::assign(const size_t counts)
{
for (size_t i = 0u; i < counts; i++)
{
// 创建线程
m_threadList.push_back(new WorkThread());
}
}
void LeisureThreadList::push(WorkThread * thread)
{
if (nullptr == thread)
return;
m_mutexThread.lock();
m_threadList.push_back(thread);
m_mutexThread.unlock();
}
WorkThread * LeisureThreadList::top()
{
WorkThread * thread;
m_mutexThread.lock();
if (m_threadList.empty())
thread = nullptr;
thread = m_threadList.front();
m_mutexThread.unlock();
return thread;
}
void LeisureThreadList::pop()
{
m_mutexThread.lock();
if (!m_threadList.empty())
m_threadList.pop_front();
m_mutexThread.unlock();
}
size_t LeisureThreadList::size()
{
size_t counts = 0u;
m_mutexThread.lock();
counts = m_threadList.size();
m_mutexThread.unlock();
return counts;
}
void LeisureThreadList::stop()
{
m_mutexThread.lock();
for (auto thread : m_threadList)
{
thread->stop();
}
m_mutexThread.unlock();
}
bool LeisureThreadList::isExecuting()
{
bool ret = false;
m_mutexThread.lock();
for (auto i : m_threadList)
{
if (i->isExecuting())
{
ret = true;
break;
}
}
m_mutexThread.unlock();
return ret;
}
// 线程池
ThreadPool::ThreadPool(const size_t counts) : m_leisureThreadList{ counts }, m_bExit{ false }
{
m_threadCounts = counts;
m_bRunning.store(true);
m_bEnd.store(true);
m_thread = std::thread(&ThreadPool::run, this);
}
ThreadPool::~ThreadPool()
{
if (!m_bExit)
exit();
}
size_t ThreadPool::threadCounts()
{
return m_threadCounts.load();
}
bool ThreadPool::isRunning()
{
return m_bRunning.load();
}
void ThreadPool::run()
{
#ifndef _WIN32
prctl(PR_SET_NAME, "task_pool", 0, 0, 0);
#endif
while (true)
{
if (!m_bEnd.load())
{
m_taskMutex.lock();
if (m_taskList.empty())
{
m_taskMutex.unlock();
break;
}
m_taskMutex.unlock();
}
// 暂停执行,则阻塞
{
std::unique_lock<std::mutex> lockRunning(m_runningMutex);
m_condition_running.wait(lockRunning,
[this]() {return this->m_bRunning.load(); });
}
WorkThread *thread = nullptr;
Task * task = nullptr;
{
std::unique_lock<std::mutex> lock(m_taskMutex);
// 如果没有任务且没有结束则阻塞
m_condition_task.wait(lock,
[this]() {
return !(this->m_taskList.empty() && this->m_bEnd.load());
});
if (!m_taskList.empty())
{
task = m_taskList.front();
m_taskList.pop();
}
}
// 选择空闲的线程执行任务
int32_t cycleSize = 0;
thread = m_leisureThreadList.top();
while (thread->isExecuting())
{
cycleSize++;
if (cycleSize >= m_leisureThreadList.size())
{
cycleSize = 0;
msleep(10); //防止空耗cpu
}
m_leisureThreadList.pop();
m_leisureThreadList.push(thread);
thread = m_leisureThreadList.top();
}
m_leisureThreadList.pop();
m_leisureThreadList.push(thread);
//do {
// thread = m_leisureThreadList.top();
// m_leisureThreadList.pop();
// m_leisureThreadList.push(thread);
//} while (thread->isExecuting());
// 通知线程执行
thread->assign(task);
thread->notify();
}
}
void ThreadPool::addTask(Task *task)
{
if (nullptr == task)
return;
m_taskMutex.lock();
m_taskList.push(task);
m_taskMutex.unlock();
// 通知相关等待线程
m_condition_task.notify_one();
}
void ThreadPool::start()
{
m_bRunning.store(true);
m_condition_running.notify_one();
}
void ThreadPool::stop()
{
m_bRunning.store(false);
}
void ThreadPool::exit()
{
m_bEnd.store(false);
// 这里必须在设置标志位后,且必须notify,因为线程
// 有时候会在设置标志位后还处于阻塞状态
m_condition_task.notify_all();
// 锁定线程
m_mutexThread.lock();
if (m_thread.joinable())
{
m_thread.join();
}
m_mutexThread.unlock();// 解锁线程
// 任务线程同步停止,当然会在执行线程退出前把线程中的任务执行完成
m_leisureThreadList.stop();
m_bExit = true;
}
void ThreadPool::msleep(unsigned long timems)
{
#ifdef WIN32
::Sleep(timems);
#else
timespec tv;
tv.tv_sec = timems / 1000;
tv.tv_nsec = (timems % 1000) * 1000 * 1000;
nanosleep(&tv, nullptr);
#endif
}
main.cpp
#include <iostream>
#include "ThreadPool.h"
//测试线程池任务
class PrintTask : public Task
{
public:
PrintTask() {}
void run()
{
auto id = std::this_thread::get_id();
printf("hello world: %d!\n", id);
}
};
int main()
{
//创建线程池对象,该对象会开辟5个线程
ThreadPool m_pool(5);
//将任务添加到线程池里等待执行,new之后不需要手动释放,线程池会在该任务完成后自动释放
m_pool.addTask(new PrintTask());
m_pool.addTask(new PrintTask());
m_pool.addTask(new PrintTask());
m_pool.addTask(new PrintTask());
m_pool.addTask(new PrintTask());
m_pool.addTask(new PrintTask());
m_pool.addTask(new PrintTask());
return 0;
}
main.cpp里演示了如何使用该线程池,一次运行结果如下:
hello world: 26660!
hello world: 15548!
hello world: 15548!
hello world: 24756!
hello world: 22540!
hello world: 26660!
hello world: 26528!