C++线程池(1)理论基础及简单实现

news2024/11/17 17:54:52

写过CURD的程序员肯定都熟悉“数据库连接池”这个东西,数据库连接是个昂贵的东西,因为它的创建过程比较耗费资源。所以为了节约资源、提高数据库操作的性能,“数据库连接池”就应运而生了。

其实线程池跟数据库连接池类似,一个线程的创建和销毁也需要耗费资源,如果反复地创建和删除线程,那么资源的浪费也是很可观的。所以,预先创建一些线程,在有任务的时候分配给相应的线程去执行,对提高系统的性能有非常积极的作用。

一、线程池的创建过程

1、创建线程

for (size_t i = 0; i < threadCount; ++i) {
    threads.emplace_back(threadFunc, this);
}

threads是个vector,我们可以先创建一些线程,并把这些线程放到一个vector中。

2、任务队列与调度

void ThreadPool::addTask(const Task& task) {
    {
        lock_guard<mutex> lock(queueMutex);
        taskQueue.emplace(task);
    }
    condition.notify_one();
}

新进来的任务会被加到taskQueue队列中,你可以根据一定的策略从taskQueue中取出任务,交给某个空闲的线程去执行。

3、线程执行及回收

void ThreadPool::threadFunc() {
    while (true) {
        Task task;
        {
            unique_lock<mutex> lock(queueMutex);
            //wait()用来等一个东西
            //1. 如果第2个参数为false,就跟只有1个参数一样,直接阻塞到本行
            //阻塞到什么时候为止呢?有其他持有锁的线程notify()后.
            //2. 当wait()被notify()唤醒后,会先判断第2个参数是否为true,如果为true才会
            //继续往下执行.
            condition.wait(lock, [this]() { return !taskQueue.empty() || terminate; });

            if (terminate && taskQueue.empty()) {
                break;
            }

            task = taskQueue.front();
            taskQueue.pop();
        }
        task(); // Execute the task.
    }
}

4、线程池的优雅终止

在多线程程序中,如何终止线程是一个需要“慎重”考虑的问题,你不能直接给stop了,因为这会导致某个正在运行的线程被硬生生地中断,会导致内部数据的不一致!

我们需要先添加一个标志:terminate,当terminate标记为true时,先把线程池的while任务给停了,然后唤醒所有等待中的线程,使用std::thread::join()函数等待线程执行完毕。

ThreadPool::~ThreadPool() {
    terminate = true;
    condition.notify_all(); // 唤醒所有等待中的线程

    for (thread& th : threads) {
        if (th.joinable()) {
            th.join(); // 等待线程执行完毕
        }
    }
}

二、一个线程池的例子


#include <vector>
#include <queue>
#include <thread>
#include <iostream>
#include <stdexcept>
#include <condition_variable>
#include <memory>

#define MAX_THREADS 8 //最大线程数目

class Task{
public:
    void process(){
	
	}
};

class ThreadPool {
public:
    ThreadPool() = default;
	
    ThreadPool(const ThreadPool &) = delete;
    ThreadPool(ThreadPool &&) = delete;
    ThreadPool &operator = (const ThreadPool &) = delete;
    ThreadPool &operator = (ThreadPool &&) = delete;

	//初始化所有线程并启动线程
	void init(int threadCount){
		if (threadCount <= 0 || threadCount > MAX_THREADS) {
			throw std::exception();
		}

		for (int i = 0; i < threadCount; i++) {
			// emplace_back不能先构造再插入. 
			// 方法2: std::thread temp(worker, this); work_threads.push(temp);
			work_threads.emplace_back(worker, this);
		}
	}

	bool dispatch(std::shared_ptr<Task>&& request){
		// 操作工作队列时一定要加锁, 因为他被所有线程共享
		std::unique_lock<std::mutex> lock(queue_mutex);
		lock.lock();
		tasks_queue.push(request);
		lock.unlock();

		// 线程池添加进去了任务, 通知等待的线程
		condition.notify_one();

		return true;
	}

	~ThreadPool(){
		std::unique_lock<std::mutex> lock(queue_mutex);
		stop = true;
		 
		condition.notify_all();
		for (auto& w : work_threads) {
			w.join();
		}
	}

private:
	// 工作线程需要运行的函数,不断的从任务队列中取出并执行
	static void* worker(void* arg){
		ThreadPool* pool = (ThreadPool*)arg;
		pool->run();

		return pool;
	}

	void run(){
		while (!stop)
		{
			// unique_lock() 出作用域会自动解锁
			std::unique_lock<std::mutex> lk(this->queue_mutex);

			// 如果任务队列不为空,就停下来等待唤醒
			this->condition.wait(lk, [this] {
				return !this->tasks_queue.empty(); 
			});

			std::shared_ptr<Task> request = tasks_queue.front();
			tasks_queue.pop();
			if (request) {
				request->process();
			}
		}
	}

private:
	// 工作线程
	std::vector<std::thread> work_threads;
	
	// 任务队列
	std::queue<std::shared_ptr<Task>> tasks_queue;

	std::mutex queue_mutex;
	std::condition_variable condition;
	
	bool stop = false;
};

int main(){
    ThreadPool pool;
	pool.init(4);
	
    return 0;
}

参考:

(1)基于C++11实现线程池

(2)轻松掌握C++线程池:从底层原理到高级应用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/707241.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《PyTorch深度学习实践》第七讲 处理多维特征的输入

b站刘二大人《PyTorch深度学习实践》课程第七讲处理多维特征的输入笔记与代码&#xff1a;https://www.bilibili.com/video/BV1Y7411d7Ys?p7&vd_sourceb17f113d28933824d753a0915d5e3a90 Diabetes Dataset 每一行是一个记录每一列是一个特征&#xff0c;每个样本有8个特征…

为什么我们家里的IP都是192.168开头的?

为什么我们家里的IP都是192.168开头的&#xff1f; 本文为掘金社区首发签约文章&#xff0c;14天内禁止转载&#xff0c;14天后未获授权禁止转载&#xff0c;侵权必究&#xff01; 是的&#xff0c;还是我小白&#xff0c;什么技术博主&#xff0c;老情感博主了。 来讲个故事。…

网络安全合规-数据安全分类分级

数据安全是指保护数据免受未经授权的访问、使用、泄露、破坏或篡改的措施。数据安全包括物理安全、网络安全、应用程序安全、数据备份和恢复等方面。 数据分级分类是指根据数据的重要性和敏感程度&#xff0c;将数据划分为不同的级别&#xff0c;并根据不同级别的数据制定不同…

enote笔记法之附录1——“语法词”(即“关联词”)(ver0.23)

enote笔记法之附录1——“语法词”&#xff08;即“关联词”&#xff09;&#xff08;ver0.23&#xff09; 最上面的是截屏的完整版&#xff0c;分割线下面的是纯文字版本&#xff1a; 作者姓名&#xff08;本人的真实姓名&#xff09;&#xff1a;胡佳吉 居住地&#xff1…

前言-----

因要参加电赛&#xff0c;接触到STC89C52RC&#xff08;A51&#xff09;单片机 STC89C52RC引脚功能 1电源: ①VCC - 芯片电源&#xff0c;接5V&#xff1b; ②VSS - 接地端&#xff1b; 2.时钟: XTAL1、XTAL2 - 晶体振荡电路反相输入端和输出端。 3.控制线: 控制线共…

Java 17官方编程手册都针对哪些方面做了更新?

Java 17&#xff0c;官方编程手册&#xff0c; 《International Developer》杂志称为“全世界醉著名的编程书籍创作者之一”的Herbert Schildt倾情解读 《Java官方编程手册》从1996年首次出版以来&#xff0c;已经经历了数次改版&#xff0c;每次改版都反映 了Java不断演化的进…

分享解析,2+1链动模式为何能在市场上经久不衰

​小编介绍&#xff1a;10年专注商业模式设计及软件开发&#xff0c;擅长企业生态商业模式&#xff0c;商业零售会员增长裂变模式策划、商业闭环模式设计及方案落地&#xff1b;扶持10余个电商平台做到营收过千万&#xff0c;数百个平台达到百万会员&#xff0c;欢迎咨询。 随…

服务网格:Istio 架构

什么是服务网格 服务网格(Service Mesh)这个术语通常用于描述构成这些应用程序的微服务网络以及应用之间的交互。随着规模和复杂性的增长&#xff0c;服务网格越来越难以理解和管理。 它的需求包括服务发现、负载均衡、故障恢复、指标收集和监控以及通常更加复杂的运维需求&am…

数据结构--双端队列

数据结构–双端队列 双端队列&#xff08;Double-ended Queue&#xff0c;简称Deque&#xff09;是一种具有队列和栈特性的数据结构&#xff0c;可以在队列的两端进行插入和删除操作。双端队列允许从前端和后端同时进行插入和删除操作&#xff0c;因此可以称为“两端都可以进出…

「STC8A8K64D4开发板」第2-6讲:串口通信

第2-6讲&#xff1a;串口通信 学习目的掌握USB转串口电路的原理和设计。学习STC8A8K64D4的串口通信&#xff0c;包括串口初始化、波特率计算、串口发送和接收。编写串口收发程序&#xff0c;尤其是串口接收的软件缓存处理。编写串口发送命令控制LED指示灯亮灭的程序。 硬件电路…

【电商API接口系列】店铺所有商品数据的采集

API接口允许不同应用程序之间共享数据&#xff0c;在系统之间传输、读取和更新数据。例如&#xff0c;一个电商网站可以通过API接口获取支付系统的支付状态。API接口允许开发人员使用他人开发的功能来扩展自己的应用程序。通过调用第三方API接口&#xff0c;开发人员无需重新实…

二进制部署Kubernetes

二进制部署Kubernetes v1.20 k8s集群master01&#xff1a;192.168.142.10 kube-apiserver kube-controller-manager kube-scheduler etcd k8s集群master02&#xff1a;192.168.142.20 k8s集群node01&#xff1a;192.168.142.30 kubelet kube-proxy docker k8s集群node…

基于Java汽车售票网站设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

VUE_网页自定义右键菜单组件

可以在uni-app或vue脚手架项目使用 引入组件会接管页面右键事件&#xff0c;所有options为空数组时&#xff0c;在页面右键将没有反应 rightMenu.vue <template><view><view v-if"show" class"contextMenu" :style"lay_style"…

Kafka:Kafka资料整理

一、官网 二、博主文章 1、kafka是什么 • Worktile社区 三、源码解读

一文了解云计算

目录 &#x1f34e;云服务 &#x1f34e;云计算类型 &#x1f352;公有云 &#x1f352;私有云 &#x1f352;混合云 &#x1f34e;云计算服务模式 &#x1f352;IaaS基础设施即服务 &#x1f352;PaaS平台即服务 &#x1f352;SaaS软件即服务 &#x1f352;三者之间区别 &…

4.springboot原理篇

原理篇 spring与springboot区别 spring是承载容器 springboot做的主要工作&#xff1a; ①简化配置&#xff08;省去了spring中配置xml&#xff0c;引入application.yml文件&#xff09; ②为我们提供了 spring-boot-starter-web 依赖&#xff0c;这个依赖包含了Tomcat和sprin…

二进制搭建Kubernetes集群(二)——部署Worker Node 组件

四.部署node节点 4.1 所有node节点部署 docker引擎 #所有 node 节点部署docker引擎#安装依赖包yum install -y yum-utils device-mapper-persistent-data lvm2#设置阿里云镜像源yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker- ce.…

Nuget更新全局包、缓存和临时文件夹路径位置

Nuget更新缓存 1、查看默认的Nuget路径2、更改全局包路径2.1 通过环境变量来进行修改2.2通过Nuget.Config配置文件来进行修改 3、更改http-cache路径4、更改temp文件路径5、更改plugins-cache文件路径 NuGet是一个流行的软件包管理器&#xff0c;可以帮助.NET开发人员轻松地添加…

【Python】 【Pandas 】【read_csv()】Pandas库的read_csv()方法的使用,处理:None,NULL

近期&#xff0c;使用read_csv的时候&#xff0c;遇到一个问题&#xff0c;就是本地读取的csv文件中的数据有None和NaN 两种&#xff0c;如&#xff1a; 直接使用 pd.read_csv(rF:\我爱Python\预测\历史样本.csv,encodingutf-8)发现读取的数据是将None 和 NULL 直接处理成 NaN…