本篇文章用来学习和实现轻松掌握C++线程池:从底层原理到高级应用。
代码如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <deque>
#include <queue>
#include <atomic>
#include <chrono>
using namespace std;
struct Task {
Task(int id) : id_(id){};
void exec() {
std::cout << "Task " << id_ << " is doing" << std::endl;
}
private:
int id_;
};
class ThreadPool {
public:
ThreadPool(size_t threadCount) : terminate(false), threadCount(threadCount), taskCount(0), completedTaskCount(0)
{//构造函数,创建线程池,将每个线程添加到vector里。
for (size_t threadNum = 0; threadNum < threadCount; ++threadNum) {
//成员函数指针,必须在前面加&,普通函数若想表示函数指针,可加&也可以不加。学一下构造thread的几种方法。
threads.emplace_back(&ThreadPool::threadFunc, this, threadNum);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
~ThreadPool() {
terminate = true;
condition.notify_all();
for (thread& th : threads) {
if (th.joinable()) {
th.join();
}
}
}
void addTask(const Task& task) {
{
taskQueue.push(task);//或者用emplace(task)
taskCount++;
}
condition.notify_one();//每往任务队列中加入一个任务,就通知线程池处理一个
}
size_t getCompletedTaskCount() const {
return completedTaskCount.load();
}
private:
void threadFunc(int threadNum) {
std::cout << "threadFunc " << threadNum << " is ready" << "\n";
while (true) {
unique_lock<mutex> lock(queueMutex);
//几个线程都在这里排队等待,当外部调用condition.notify_one();时,操作系统一般是调用排队的最前面的线程
//当其它地方发出notify时,它先通过lambda表达式,判断如果return true,就继续执行,否则进入等待状态。
//比如taskQueue不为空,或者terminate为true,就继续往下执行。
condition.wait(lock, [this]() { return (!taskQueue.empty()) || terminate; });
if (terminate && taskQueue.empty()) {
break;
}
Task task = taskQueue.front();
taskQueue.pop();
task.exec(); //里面内容随便填,表示当前task可以用了
std::cout << "This task is doing with threadNum " << threadNum << "\n" << "\n";
++completedTaskCount;
//由于这里是死循环,每个线程处理完后,又重新进入循环,排队等待后面的task的到来,相当于做了线程回收
}
}
std::vector<std::thread> threads;
//定义queue队列来存任务,FIFO结构,先存的任务先处理
std::queue<Task> taskQueue;
//定义互斥锁和条件变量,用于线程池里,等待task到来
std::mutex queueMutex;
std::condition_variable condition;
public:
//下面2个变量不需要定义原子操作,直接访问更简洁
size_t threadCount;
size_t taskCount;
private:
//下面2个变量定义原子变量,保证线程安全
atomic<bool> terminate;
atomic<size_t> completedTaskCount;
};
int main() {
ThreadPool pool(3);
std::cout << "ThreadPool pool OK" << "\n\n";
for (int i = 0; i < 5; ++i) {
Task task(i);
pool.addTask(task);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::cout << "Thread count: " << pool.threadCount << "\n";
std::cout << "Task count: " << pool.taskCount << "\n";
std::cout << "Completed task count: " << pool.getCompletedTaskCount() << "\n\n";
return 0;
}
执行时要加-pthread,结果是:
$ g++ -pthread 线程池.cpp
$ ./a.out
threadFunc 0 is ready
threadFunc 1 is ready
threadFunc 2 is ready
ThreadPool pool OK
Task 0 is doing
This task is doing with threadNum 0
Task 1 is doing
This task is doing with threadNum 1
Task 2 is doing
This task is doing with threadNum 2
Task 3 is doing
This task is doing with threadNum 0
Task 4 is doing
This task is doing with threadNum 1
Thread count: 3
Task count: 5
Completed task count: 5
注:
①代码中的threads.emplace_back不可以替代为thread.push_back。
因为push_back 接受容器的元素类型的对象,并将其拷贝(左值)或移动(右值)到容器的末尾。
而emplace_back接受构造函数的参数,并在容器的末尾直接构造一个新元素,而不需要创建临时对象。
括号里的内容是参数,不是对象。
②原文章中用了优先队列priority,我在这里为了简单,用的是queue。
关于优先队列,task本身没有定义顺序。这里每次队列里有一个任务时就处理了,并未体现出多个任务需要调度时,按优先级高的先进行处理。但这么定义排序的写法是没错的。举个例子:
#include <iostream>
#include <queue>
#include <vector>
// 自定义比较函数 LessByPriority
struct LessByPriority {
bool operator()(int a, int b) {
return a < b; // 优先级低的排在前面,如果要优先级高的排在前面,则改为 a < b
}
};
int main() {
std::priority_queue<int, std::vector<int>, LessByPriority> pq;
// 逐个插入元素
pq.push(5);
pq.push(7);
pq.push(2);
pq.push(9);
pq.push(3);
// 输出队列中排好序的结果
std::cout << "Priority Queue sorted result: ";
while (!pq.empty()) {
std::cout << pq.top() << " ";
pq.pop();
}
std::cout << std::endl;
return 0;
}
结果是:
Priority Queue sorted result: 9 7 5 3 2