C++线程池

article/2025/9/10 8:34:22

1.基础概念
线程池:一种线程的使用模式,线程过多会带来调度开销,进而影响缓存局部性和整体性。而线程池维护着多个线程,等待监督管理者分配可并行执行的任务。这样避免了在短时间内创建和销毁线程的代价。线程池不仅能够内核的充分利用,还能防止过分调度。可用的线程数据取决于可用的并发处理器,处理内核,内存,网络sockets等数量。
2.线程池的组成
2.1 线程池管理器
创建一定数量的线程,启动线程,调配任务,管理着线程池。
线程池目前只需要Start()启动方法,Stop()方法,AddTask()方法。
Start():创建一定数量的线程,进入线程循环。
Stop():停止线程循环,回收所有的线程。
AddTask():添加任务。
2.2 工作者线程
线程池中线程,在线程池中等待并执行任务。
该文使用条件变量condition_variable实现等待和唤醒进制。
2.3 任务接口
添加任务接口,以供工作线程的调度任务和执行。
2.4 任务队列
用于存放没有处理的任务,提供一种缓存机制。
3.线程池工作的四种状态
假设目前我们的线程池大小为3,任务队列的大小我们不做限制。
3.1 主线程当前没有任务要执行,线程池中任务队列为空。
下面所有的情况工作线程处于空闲状态,任务缓冲队列为空。在这里插入图片描述3.2 我们在3.1 的基础上添加小于等于线程池中线程数量的的任务数。
基于3.1的情况,所有的工作线程处于等待状态,我们给主线程添加3个任务,然后通知(notify())线程池中的线程开始取(Take())任务开始执行,此时的任务缓冲队列还是空。
3.3 主线程添加任务数量大于当前线程池中线程的数量。
基于3.2的情况,线程池中所有的工作线程都处于工作状态,主线程开始添加第四个任务,发现线程池中没有空闲线程,于是将任务存入缓冲队列。工作线程空闲后,主动从任务队列中获取任务执行。
在这里插入图片描述3.4 主线程添加任务数量大于当前线程池中线程的数量,且任务队列已满。
主线程添加第N个任务,添加后发现线程池中的线程已经用完,并且任务队列已满。于是主线程进入等待状态,等待任务队列腾空通知,这种情况会阻塞主线程。
在这里插入图片描述
4.线程池的C++实现:
在这里插入图片描述
由上可知,线程池由3部分组成:
(1) 任务队列(Task Queue):存储需要处理的任务,由工作者线程来处理这些任务。
①通过线程池提供的API函数,将任务添加到任务队列或者从任务队列中删除任务。
②已处理的任务会从任务队列中删除。
③线程池的使用者,也就是往任务队列中添加任务的线程就是生产者线程。
(2) 工作者线程(任务队列任务的消费者,N个)
①线程池中维护了一定数量的工作者线程,他们的任务是不断的读取任务队列中的任务,并且取出执行。
②工作的线程相当于任务的消费者角色。
③如果任务队列为空,工作者线程会被阻塞。(使用条件变量/信号量阻塞)
④一旦任务队列有任务了,由生产者将任务队列解除,工作者线程开始工作。
(3) 管理者线程
①它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作者线程个数进行检测。
②当任务数量过多时,可以适当的创建一些新的工作线程。
②当任务过少时,可以适当的销毁一些工作的线程。
5.下面来看具体的代码
1.任务队列类
ThreadPool.h

#include <thread>
#include <mutex>
#include <queue>
#include <vector>
#include <condition_variable>using namespace std;
using namespace std::literals::chrono_literals;
using callback = void(*)(void*);// 任务队列类
// 成员函数介绍:参数1:任务执行函数;参数2:任务执行函数的参数
class Task
{
public:callback function;void* arg;
public:Task(callback f, void* arg){ function = f; this->arg = arg; }
};
// 线程池类
class ThreadPool
{
public:ThreadPool(int min, int max);// 添加任务void Add(callback f, void* arg);void Add(Task task);// 忙线程个数int Busynum();// 存活线程个数int Alivenum();~ThreadPool();private:// 任务队列queue<Task> taskQ;thread managerID;   //管理者线程IDvector<thread> threadIDs;   //int minNum;   //最小线程数int maxNum;   //最大线程数int busyNum;   //忙的线程数int liveNum;    //存活的线程数int exitNum;    //要销毁的线程数mutex mutexPool;    //整个线程池的锁condition_variable cond;     //任务队列是否为空,阻塞工作者线程bool shutdown;    //是否销毁线程池,销毁为1,不销毁为0static void manager(void* arg);   //管理者线程static void worker(void* arg);   //工作线程
};

线程池类具体实现
ThreadPool.cpp

#include "threadpool.h"
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
const int NUMBER = 2;ThreadPool::ThreadPool(int min, int max)
{do{minNum = min;maxNum = max;busyNum = 0;liveNum = min;exitNum = 0;shutdown = false;// this:传递给线程入口函数的参数,即线程池managerID = thread(manager, this);threadIDs.resize(max);for (int i = 0; i < min; ++i){threadIDs[i] = thread(worker, this);}return;} while (0);
}ThreadPool::~ThreadPool()
{shutdown = true;//阻塞回收管理者线程if (managerID.joinable()) managerID.join();//唤醒阻塞的消费者线程cond.notify_all();for (int i = 0; i < maxNum; ++i){if (threadIDs[i].joinable()) threadIDs[i].join();}
}void ThreadPool::Add(Task t)
{unique_lock<mutex> lk(mutexPool);if (shutdown){return;}//添加任务taskQ.push(t);cond.notify_all();
}void ThreadPool::Add(callback f, void* a)
{unique_lock<mutex> lk(mutexPool);if (shutdown){return;}//添加任务taskQ.push(Task(f, a));cond.notify_all();
}int ThreadPool::Busynum()
{mutexPool.lock();int busy = busyNum;mutexPool.unlock();return busy;
}int ThreadPool::Alivenum()
{mutexPool.lock();int alive = liveNum;mutexPool.unlock();return alive;
}void ThreadPool::worker(void* arg)
{ThreadPool* pool = static_cast<ThreadPool*>(arg);// 工作者线程需要不停的获取线程池任务队列,所以使用whilewhile (true){// 每一个线程都需要对线程池进任务队列行操作,因此线程池是共享资源,需要加锁unique_lock<mutex> lk(pool->mutexPool);// 当前任务队列是否为空while (pool->taskQ.empty() && !pool->shutdown){// 如果任务队列中任务为0,并且线程池没有被关闭,则阻当前工作线程pool->cond.wait(lk);// 判断是否要销毁线程,管理者让该工作者线程自杀if (pool->exitNum > 0){pool->exitNum--;if (pool->liveNum > pool->minNum){pool->liveNum--;cout << "threadid: " << std::this_thread::get_id() << " exit......" << endl;// 当前线程拥有互斥锁,所以需要解锁,不然会死锁lk.unlock();return;}}}// 判断线程池是否关闭了if (pool->shutdown){cout << "threadid: " << std::this_thread::get_id() << "exit......" << endl;return;}// 从任务队列中去除一个任务Task task = pool->taskQ.front();pool->taskQ.pop();pool->busyNum++;// 当访问完线程池队列时,线程池解锁lk.unlock();// 取出Task任务后,就可以在当前线程中执行该任务了cout << "thread: " << std::this_thread::get_id() << " start working..." << endl;task.function(task.arg);//(*task.function)(task.arg);free(task.arg);task.arg = nullptr;// 任务执行完毕,忙线程解锁cout << "thread: " << std::this_thread::get_id() << " end working..." << endl;lk.lock();pool->busyNum--;lk.unlock();}
}// 检测是否需要添加线程还是销毁线程
void ThreadPool::manager(void* arg)
{ThreadPool* pool = static_cast<ThreadPool*>(arg);// 管理者线程也需要不停的监视线程池队列和工作者线程while (!pool->shutdown){//每隔3秒检测一次//sleep(3);std::this_thread::sleep_for(std::chrono::seconds(3));// 取出线程池中任务的数量和当前线程的数量,别的线程有可能在写数据,所以我们需要加锁// 目的是添加或者销毁线程unique_lock<mutex> lk(pool->mutexPool);int queuesize = pool->taskQ.size();int livenum = pool->liveNum;int busynum = pool->busyNum;lk.unlock();//添加线程//任务的个数>存活的线程个数 && 存活的线程数 < 最大线程数if (queuesize > livenum && livenum < pool->maxNum){// 因为在for循环中操作了线程池变量,所以需要加锁lk.lock();// 用于计数,添加的线程个数int count = 0;// 添加线程for (int i = 0; i < pool->maxNum && count < NUMBER && pool->liveNum < pool->maxNum; ++i){// 判断当前线程ID,用来存储创建的线程IDif (pool->threadIDs[i].get_id() == thread::id()){cout << "Create a new thread..." << endl;pool->threadIDs[i] = thread(worker, pool);// 线程创建完毕count++;pool->liveNum++;}}lk.unlock();}//销毁线程:当前存活的线程太多了,工作的线程太少了//忙的线程*2 < 存活的线程数 && 存活的线程数 >  最小的线程数if (busynum * 2 < livenum && livenum > pool->minNum){// 访问了线程池,需要加锁lk.lock();// 一次性销毁两个pool->exitNum = NUMBER;lk.unlock();// 让工作的线程自杀,无法做到直接杀死空闲线程,只能通知空闲线程让它自杀for (int i = 0; i < NUMBER; ++i) pool->cond.notify_all();  // 工作线程阻塞在条件变量cond上}}
}
  1. 测试代码
#include "threadpool.h"
#include <iostream>
#include <stdlib.h>using namespace std;void taskFunc(void* arg)
{int nNum = *(int*)arg;cout << "thread: " << std::this_thread::get_id() << ", number=" << nNum << endl;std::this_thread::sleep_for(std::chrono::seconds(1));
}int main()
{// 设置线程池最小5个线程,最大10个线程ThreadPool pool(5, 10);int i;// 往任务队列中添加100个任务for (i = 0; i < 100; ++i){int* pNum = new int(i + 100);pool.Add(taskFunc, (void*)pNum);}for (; i < 200; ++i){std::this_thread::sleep_for(std::chrono::seconds(1));int* pNum = new int(i + 100);pool.Add(taskFunc, (void*)pNum);}return 0;
}

4.测试结果如下图:
在这里插入图片描述
本文转载自爱编程的大丙
参考文献:
BrianX
晓枫寒叶


http://chatgpt.dhexx.cn/article/IwG8yIaE.shtml

相关文章

线程池详解

成功不是将来才有的&#xff0c;而是从决定去做的那一刻起&#xff0c;持续累积而成。 目录 背景 线程池介绍 线程池使用 Executors 线程池如何关闭&#xff1f; 面试题 总结 背景 下面是一段创建线程并运行的代码: for (int i 0; i < 100; i) {new Thread(() -&…

线程池(通俗易懂)

目录 一、什么是线程池 二、创建线程池的方式 三、线程池的七大参数 四、四种拒绝策略 1.AbortPolicy() 2.CallerRunsPolicy() 3.DiscardPolicy() 4.DiscardOldestPolicy() 五、自定义一个线程池 1.场景描述 2.代码实现 一、什么是线程池 线程池其实就是一种多线程处理…

线程池研发学习笔记

线程池研发 线程池 线程池基础 概念介绍 1:什么是线程池 可以直接叙述,也可以对比连接池介绍 线程池其实就是一种多线程处理形式&#xff0c;处理过程中可以将任务添加到队列中&#xff0c;然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是…

线程池是什么?线程池(ThreadPoolExecutor)使用详解

点一点&#xff0c;了解更多https://www.csdn.net/ 本篇文章将详细讲解什么是线程池&#xff0c;线程池的参数介绍&#xff0c;线程池的工作流程&#xff0c;使用Executors创建常见的线程池~~~ 目录 点一点&#xff0c;了解更多 文章目录 一、线程池的概念 1.1线程池的目的…

写给小白看的线程池,还有10道面试题

如何搞定20k的面试小抄 为什么要用线程池呢&#xff1f; 下面是一段创建线程并运行的代码: for (int i 0; i < 100; i) {new Thread(() -> {System.out.println("run thread->" Thread.currentThread().getName());userService.updateUser(....);}).start…

线程池详解(通俗易懂超级好)

目标 【理解】线程池基本概念 【理解】线程池工作原理 【掌握】自定义线程池 【应用】java内置线程池 【应用】使用java内置线程池完成综合案例 线程池 线程池基础线程池使用线程池综合案例学员练习线程池总结 概念介绍 什么是线程池为什么使用线程池线程池有哪些优势 什么…

Java 多线程:彻底搞懂线程池

熟悉 Java 多线程编程的同学都知道&#xff0c;当我们线程创建过多时&#xff0c;容易引发内存溢出&#xff0c;因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列&#xff08;workQueue&#x…

GridView概述

一、使用GridView以表格形式显示多张图片 GridView用于在界面上按行、列分布的方式来显示多个组件 二、使用GridView 1、java代码 import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterV…

Master-Detail GridView

梦幻版Master-Detail GridView(黄忠成) 2007-12-26 09:34 前面的Master-Detail GridView控件應用&#xff0c;相信你已在市面上的書、或網路上見過&#xff0c;但此節中的GridView控件應用包你沒看過&#xff0c;但一定想過&#xff01;請見圖4-8-63。 圖4-8-63 圖 4-8-64 你一…

GridView DataGrid

ASP.NET 2.0提供了功能强大的数据绑定控件GridView、在使用中&#xff0c;一些属性和方法经常会与ASP.NET 1.1中的DataGrid混淆(VS2005中依然可以使用DataGrid&#xff0c;手动添加到工具箱或HTML状态输入代码)&#xff0c;下面我们分别用GridView和DataGrid实现其数据绑定、编…

GridView详讲

GridView是ASP.NET界面开发中的一个重要的控件&#xff0c;对GridView使用的熟练程度直接影响软件开发的进度及功能的实现。(车延禄) GridView的主要新特性&#xff1a; 1.与DataSource控件结合实现了显示与数据操作的分离&#xff0c;大大减化了代码的编写量; 2.实现"双向…

GridView详述

GridView无代码分页排序GridView选中&#xff0c;编辑&#xff0c;取消&#xff0c;删除GridView正反双向排序GridView和下拉菜单DropDownList结合GridView和CheckBox结合鼠标移到GridView某一行时改变该行的背景色方法一鼠标移到GridView某一行时改变该行的背景色方法二GridVi…

GridView、ListView、Adapter、Map、HashMap

1.ListView自定义适配器adapter 注&#xff1a;Android适配器是数据和视图之间的桥梁&#xff0c;以便于数据在View上显示。适配器就像显示器&#xff0c;把复杂的东西按人可以接受的方式来展现。 &#xff08;1&#xff09;首先将适配器的View视图表现出来&#xff0c;使用L…

GridViewPager

GridViewPager ViewPager结合GridView&#xff0c;轻松实现类似表情面板的控件。可自由定制Item布局&#xff0c;提供充足的自定义参数等。也处理了条目点击事件和条目长按事件。效果如下&#xff1a; Demo下载地址&#xff1a;GridViewPager &#xff0c;或者扫描以下二维码…

libevent 编译

1.下载源码 github:https://github.com/libevent/libevent 官网&#xff1a;http://libevent.org/ 2.CMake 编译 在libevent源码目录建立文件夹&#xff1a;BuildVs2010_x64 2.打开CMake 3.BuildVs2010_x64 下此时生成了vs2010的解决方案。然后编译生成就ok NOTE&#x…

13、《Libevent中文帮助文档》学习笔记13:Linux下集成、运行libevent

Linux下编译libevent的指导可以参考《4、《Libevent中文帮助文档》学习笔记4&#xff1a;Linux下编译libevent》&#xff0c;完成编译、安装&#xff0c;生成so库后&#xff0c;其他程序即可依赖libevent的so库&#xff0c;使用libevent的功能。 由于没有通过prefix指定安装路…

libevent 编译与安装 (WIN10 visual studio2019, ubuntu,centos)

文章目录 一、准备安装包二、编译与安装编译zlib编译openssl编译libevent 三、libevent集成zlib测试程序修改编译&#xff08;可选&#xff09;四、测试程序五、linux(ubuntu)测试安装依赖环境&#xff0c;依次编译zlib,openssl,libeventwindows与linux共享文件夹&#xff08;使…

Libevent 学习一:Libevent 源码编译

文章目录 Libevent 学习一&#xff1a;Libevent 源码编译Libevent Windows 编译Windows 编译环境安装 Visual Studio Community 2015安装 zlib安装 OpenSSL安装 Libeventcmake 安装 LibeventLibevent 测试程序 Libevent Linux编译CentOS 7 安装 LibeventLibevent 测试程序 Libe…

libevent mysql_libevent安装总结

1.先用&#xff1a;ls -al /usr/lib | grep libevent 查看是否已安装&#xff1b;如果已安装且版本低于1.3&#xff0c;则先通过&#xff1a;rpm -e libevent —nodeps进行卸载。 2.下载libevent安装包&#xff1a;libevent-2.0.18-stable.tar.gz。 wget https://github.com/do…

在window用vcpkg安装libevent

参考readme https://github.com/microsoft/vcpkg/blob/master/README_zh_CN.md 使用的PackageManager方式安装&#xff0c; Package Managers 下载 vcpkg 依赖管理包 git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.bat ./vcpkg integrate…