1、介绍
1.1 线程池应用场景
在进行创建线程任务时,如果需要频繁的创建线程、销毁线程,这样会极大地降低效率,因为创建线程也是需要时间的,一个完整的线程处理运行时间包括:线程的创建时间、线程运作时间、线程的销毁时间。
对于频繁创建线程的业务场景,我们可以预先创建多个线程,在创建多线程任务时,我们可以直接将线程函数添加到预先创建的线程中,这样就可以避免多次创建线程的时间,提高代码运行效率。
1.2 线程池设计的思路
设计多线程主要实现的功能有:(1)预设创建线程的数量。(2)存储运行一定数量线程任务容器workerThread变量(可以是vector或其他容器,元素类型是std::thread),该容器一直检测任务队列变量中是否有待执行的任务,有:则取出执行,没有:则等待。(3)用于存储运行任务的变量Task(数据类型是queue,数据类型是std::function<T>),该变量主要用于存储待运行线程函数func。
线程池理论可以参考链接如下:
深入解析C++编程中线程池的使用_c++线程池用法_歌行梅村的博客-CSDN博客
1.3 线程池设计注意事项
所需理论知识
创建具有适配所有线程函数的任务队列Task,创建这样的任务队列需要有一定的C++基础,可能需要如下知识点:
可变参数模板:template <class... Args>_我在这里啊@的博客-CSDN博客
C++多线程之旅-future等待事件_或许 没有的博客-CSDN博客
C++中函数对象模板function<T>、通用函数适配器std::bind和lambda_c++function头文件_夜雨听萧瑟的博客-CSDN博客
C++多线程中共享变量同步问题_夜雨听萧瑟的博客-CSDN博客
1.4 预设的线程数量是多少?
预设的线程数量不是说越多越好,而是创建适当数量的线程数,让CPU的利用率达到最大。如果预设的线程数量过大,PC的核数有限,这样同时只会有一小部分任务在同步运行,这样操作系统就需要不断的切换上下文,频繁的切换上下文也需要时间,这样反而会降低运行效率。
经验值
主要有下面几种:
(1) 设置线程数量的一般经验值为:2N(N是CPU核数)
(2) 2N+1(N是CPU核数)
(3) N+1(N是CPU核数)
具体设置数量可以在设置后,对其不同数量线程数运行效率进行简单的测试。
具体分析
可参考链接:
线程池创建线程数量讨论_夜雨听萧瑟的博客-CSDN博客
2、简单demo
(1)该demo的中心思想是创建10个运行的线程函数的容器workerThread,该线程不断检测任务队列中是否有待处理任务,有待处理任务则取出,执行任务。(2)创建一个管理任务队列数量容器Task,用户可以向其添加任务。
threadpoolm.h文件如下:
#pragma once
#include <mutex>
#include <condition_variable>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
class threadPoolM
{
public:
using funcType = std::function<void()>;
threadPoolM();
~threadPoolM();
void setThreadNum(int num);
void AddTask(const funcType& pf);
private:
void StartWork();
void RunTask();
int m_num;
std::vector<std::thread>workerThread;
std::mutex mut;
std::condition_variable cond;
std::queue<funcType> Task;
};
threadpoolm.cpp文件如下:
#include "threadpoolm.h"
#include <iostream>
threadPoolM::threadPoolM()
{
}
threadPoolM::~threadPoolM()
{
for(int i = 0; i < workerThread.size(); i++)
{
workerThread.at(i).join();
}
}
void threadPoolM::setThreadNum(int num)
{
m_num = num;
StartWork();
}
void threadPoolM::AddTask(const threadPoolM::funcType& pf)
{
std::unique_lock<std::mutex>lk(mut);
cond.wait(lk,[this]{return Task.size() < 10;});
Task.push(pf);
std::cout <<"id " << std::this_thread::get_id() << ", add a task, size is " << Task.size() << std::endl;
cond.notify_one();
}
void threadPoolM::StartWork()
{
for(int i = 0; i < m_num; i++)
{
workerThread.push_back(std::thread(&threadPoolM::RunTask,this));
}
}
void threadPoolM::RunTask()
{
while (true) {
std::unique_lock<std::mutex>lk(mut);
cond.wait(lk,[this]{return Task.size()>0;});
auto Ta = std::move(Task.front());
Task.pop();
Ta();
std::cout <<"id " << std::this_thread::get_id() << ", run a task,size" << Task.size() << std::endl;
cond.notify_one();
}
}
上面线程池类的使用main.cpp如下:
#include <iostream>
#include "threadpoolm.h"
int cnt = 0;
void printId(int id)
{
std::cout << "id " << std::this_thread::get_id() << ", id" << id << std::endl;
}
int main()
{
std::cout << "Hello World!" << std::endl;
threadPoolM pool;
pool.setThreadNum(10);
while(true)
{
if(cnt++ > 2000)
{
cnt = 0;
}
std::cout << "cnt " << cnt << std::endl;
auto f1 = std::bind(printId,cnt);
pool.AddTask(f1);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
//主线程中的while循环,只是对实际添加任务消息进行模拟,故没有退出while条件。
}
return 0; //由于线程池中的RunTask函数存在while循环,没有退出条件,所以该行代码不会执行。可以按照实际条件对其while条件修改。
}
运行结果如下:
上面的代码参考于
c++11最简单的线程池实现_c++实现线程池_osDetach的博客-CSDN博客
线程池的实现代码也可参考下面链接:
基于c++11的100行实现简单线程池_c++11 100行实现线程池 csdn_6plus的博客-CSDN博客
附加知识
怎样理解线程的睡眠,挂起,和阻塞? - 知乎 (zhihu.com)
“阻塞(pend)”与“挂起(suspend)”的区别?_pend group run cpu调度_zhch152的博客-CSDN博客