【Linux:线程池】
文章目录
- 1 线程池概念
- 2 第一个版本的线程池
- 3 第二个版本的线程池
- 4 第三个版本的线程池
- 5 STL中的容器以及智能指针的线程安全问题
- 6 其他常见的各种锁
- 7 读者写者问题(了解)
1 线程池概念
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池的应用场景:
- 1️⃣需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
- 2️⃣对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
- 3️⃣接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。
线程池示例:
-
- 创建固定数量线程池,循环从任务队列中获取任务对象,
-
- 获取到任务对象后,执行任务对象中的任务接口。
2 第一个版本的线程池
在创建线程池之前我们想想线程池的成员变量应该有哪些?首先我们需要一个容器来存放线程,所以不妨使用vector;还要使用一个整形变量来记录线程池中线程的个数;为了保证线程安全问题我们还得需要一把锁,同时为了维护同步关系我们还得需要一个条件变量(这里的同步关系是指当没有任务时线程库中的线程就休眠,当有任务时就执行任务);另外我们还得需要一个任务队列。这里我们再封装一个任务类让等会儿验证时效果更加明显。
Task.hpp:
#pragma once
#include <iostream>
using namespace std;class Task
{
public:Task(int x=0, int y=0, char op='+'): _x(x), _y(y), _op(op){}void run(){switch (_op){case '+':_res = _x + _y;break;case '-':_res = _x - _y;break;case '*':_res = _x * _y;break;case '/': if(_y==0){_exitCode=1;return;}_res = _x / _y;break;case '%':_res = _x % _y;break;}}void formatMsk(){cout<<"mask:"<<_x<<_op<<_y<<"==?"<<endl;}void formatRes(){cout<<"res:"<<_x<<_op<<_y<<"=="<<_res<<endl;}private:int _x;int _y;char _op;int _res = 0;int _exitCode = 0;
};
现在我们来实现第一个版本:
#pragma once
#include<iostream>
#include<vector>
#include<queue>
using namespace std;const int N=5;
template<class T>
class threadPool
{
public:threadPool(int sz=N):_sz(sz),_threads(sz){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);}static void* Routine(void* args)//用内存池的多线程执行任务{pthread_detach(pthread_self());//先让自己与主线程分离threadPool<T> *ptp=static_cast<threadPool<T> *>(args);while(true){pthread_mutex_lock(&(ptp->_mutex));while((ptp->_masks).empty()){pthread_cond_wait(&(ptp->_cond),&(ptp->_mutex));}T task=(ptp->_masks).front();(ptp->_masks).pop();pthread_mutex_unlock(&(ptp->_mutex));task.run();//在临界区外执行任务task.formatRes();}return nullptr;}void Start(){for(int i=0;i<_sz;++i){pthread_create(&_threads[i],nullptr,Routine,this);}}void PushTask(const T& task){pthread_mutex_lock(&_mutex);_masks.push(task);pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_cond);//记得唤醒休眠的线程去执行任务}~threadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}vector<pthread_t> _threads;queue<T> _masks;int _sz;//线程池中线程的个数pthread_mutex_t _mutex;pthread_cond_t _cond;
};
这里面有几个特别需要注意的点:

- Routine我们实现的是static版本的,因为创建线程所要求的函数指针与类内成员函数不吻合,类内成员函数有
this指针。 - 创建了线程后让该线程与主线程分离,也就是主线程不管新线程的资源回收了。
- 执行任务时要在临界区外执行,这样并发执行的效率才会更加高效。
- 为了方便使用将类中成员变量都搞成了公有,建议不要这样搞,可以自己写一个get。
至于其他的方面都很简单,相信大家能够很容易理解。
测试程序:
const char *ops = "+-*/%";int main()
{threadPool<Task> *threads = new threadPool<Task>(30);threads->Start();srand((size_t)time(nullptr));while (true){int x = rand() % 30 + 1;int y = rand() % 30 + 1;char op = ops[rand() % strlen(ops)];Task t(x, y, op);threads->PushTask(t);t.formatMsk();sleep(1);}return 0;
}
我们来运行下结果:

3 第二个版本的线程池
其实第二个版本的线程池与第一个的核心思路基本一致,主要是第二个版本使用的是我们自己模拟实现的创建线程的类,比如我们之前自己模拟实现(本质是封装了库中的线程库接口)的一份Thread.hpp:
#pragma once
#include <iostream>
#include <functional>
using namespace std;class threadProcess
{
public:enum stu{NEW,RUNNING,EXIT};template<class T>threadProcess(int num, T exe, void *args): _tid(0), _status(NEW),_exe(exe), _args(args){char name[26];snprintf(name, 26, "thread%d", num);_name = name;}static void* runHelper(void *args){threadProcess *ts = (threadProcess *)args; (*ts)();return nullptr;}void operator()() // 仿函数{if (_exe != nullptr)_exe(_args);}void Run(){int n = pthread_create(&_tid, nullptr, runHelper, this);if (n != 0)exit(-1);_status = RUNNING;}void Join(){int n = pthread_join(_tid, nullptr);if (n != 0)exit(-1);_status = EXIT;}string _name;pthread_t _tid;stu _status;function<void*(void*)> _exe;void *_args;
};
这样我们自己就能够用自己的线程库来完成了:
#pragma once
#include"Thread.hpp"
#include<iostream>
#include<vector>
#include<queue>
using namespace std;const int N=5;
template<class T>
class threadPool
{
public:threadPool(int sz=N):_sz(sz){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);}static void* Routine(void* args)//用内存池的多线程执行任务{//pthread_detach(pthread_self());调用自己写的线程接口不用在分离了,析构时在join掉就好了threadPool<T> *ptp=static_cast<threadPool<T> *>(args);while(true){pthread_mutex_lock(&(ptp->_mutex));while((ptp->_masks).empty()){pthread_cond_wait(&(ptp->_cond),&(ptp->_mutex));}T task=(ptp->_masks).front();(ptp->_masks).pop();pthread_mutex_unlock(&(ptp->_mutex));task.run();//在临界区外执行任务task.formatRes();}return nullptr;}void Init(){for(int i=0;i<_sz;++i){_threads.push_back(threadProcess(i+1,Routine,this));}}void Start(){for(auto& e:_threads){e.Run();}}void PushTask(const T& task){pthread_mutex_lock(&_mutex);_masks.push(task);pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_cond);//记得唤醒休眠的线程去执行任务}~threadPool(){for(auto& e:_threads){e.Join();}pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}void Check(){for(auto& e:_threads){cout<<"name:"<<e._name<<" id"<<e._tid<<endl;}}vector<threadProcess> _threads;queue<T> _masks;int _sz;//线程池中线程的个数pthread_mutex_t _mutex;pthread_cond_t _cond;
};
测试代码:
int main()
{threadPool<Task> *threads = new threadPool<Task>(8);threads->Init();threads->Start();srand((size_t)time(nullptr));while (true){int x = rand() % 30 + 1;int y = rand() % 30 + 1;char op = ops[rand() % strlen(ops)];Task t(x, y, op);threads->PushTask(t);t.formatMsk();sleep(1);}return 0;
}
运行结果:

4 第三个版本的线程池
这个版本的线程池在前面版本的基础上加了一个单例模式。因为我们发现其实线程池只需要一个就可以了,我们使用懒汉模式来创建单例。
代码实现:
#pragma once
#include "Thread.hpp"
#include <iostream>
#include <vector>
#include <queue>
using namespace std;const int N = 5;
template <class T>
class threadPool
{
public:static threadPool<T>* GetInstance(int sz=N){if(_sta_obj==nullptr){pthread_mutex_lock(&_mutex);if(_sta_obj==nullptr){_sta_obj=new threadPool<T>(sz);}pthread_mutex_unlock(&_mutex);}}static void *Routine(void *args) // 用内存池的多线程执行任务{// pthread_detach(pthread_self());调用自己写的线程接口不用在分离了,析构时在join掉就好了threadPool<T> *ptp = static_cast<threadPool<T> *>(args);while (true){pthread_mutex_lock(&(ptp->_mutex));while ((ptp->_masks).empty()){pthread_cond_wait(&(ptp->_cond), &(ptp->_mutex));}T task = (ptp->_masks).front();(ptp->_masks).pop();pthread_mutex_unlock(&(ptp->_mutex));task.run(); // 在临界区外执行任务task.formatRes();}return nullptr;}void Init(){for (int i = 0; i < _sz; ++i){_threads.push_back(threadProcess(i + 1, Routine, this));}}void Start(){for (auto &e : _threads){e.Run();}}void PushTask(const T &task){pthread_mutex_lock(&_mutex);_masks.push(task);pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_cond); // 记得唤醒休眠的线程去执行任务}~threadPool(){for (auto &e : _threads){e.Join();}pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}void Check(){for (auto &e : _threads){cout << "name:" << e._name << " id" << e._tid << endl;}}vector<threadProcess> _threads;queue<T> _masks;int _sz; // 线程池中线程的个数pthread_mutex_t _mutex;pthread_cond_t _cond;private:threadPool(int sz = N): _sz(sz){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}threadPool(const threadPool<T>& th)=delete;threadPool<T>& operator=(const threadPool<T>& th)=delete;static threadPool<T>* _sta_obj;
};
template<class T>
threadPool<T>* threadPool<T>::_sta_obj=nullptr;
其中注意点:

在加锁时为了高效我们是用了双重if条件判断。

注意将构造函数搞成了私有,拷贝构造和拷贝赋值都删掉了。
5 STL中的容器以及智能指针的线程安全问题
STL中的容器是否是线程安全的?
不是。原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全。
智能指针是否是线程安全的?
对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题。
对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数。
6 其他常见的各种锁
- 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
- 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
- 自旋锁,公平锁,非公平锁。
7 读者写者问题(了解)
读写锁:
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。
读写锁的行为:
| 当前锁的状态 | 读锁请求 | 写锁请求 |
|---|---|---|
| 无锁 | 可以 | 可以 |
| 读锁 | 可以 | 阻塞 |
| 写锁 | 阻塞 | 阻塞 |
- 注意:写独占,读共享,读锁优先级高
相关文章:
【Linux:线程池】
文章目录 1 线程池概念2 第一个版本的线程池3 第二个版本的线程池4 第三个版本的线程池5 STL中的容器以及智能指针的线程安全问题6 其他常见的各种锁7 读者写者问题(了解) 1 线程池概念 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而…...
跨境多商户中日韩英多语言商城搭建(PC+小程序+H5),搭建方案
随着全球化的推进,跨境电商正变得越来越普遍。在本文中,我们将介绍跨境电商系统开发中多语言商城独立站的部署搭建方案。 准备工作 在开始部署搭建之前,需要准备以下环境: 服务器,确保服务器具备足够的性能和稳定性。 …...
使用标准库版本编写LED闪烁
1、在STM32CubeMX中创建一个新的工程,选择STM32F103VCT6作为目标设备,并配置好所需的引脚和时钟设置。将需要用于LED连接的GPIO引脚设置为输出模式。 2、在生成代码后,打开工程目录,在Src文件夹中创建一个新的main.c文件。 3、在…...
【CDC】跨时钟域处理方法总结一
文章目录 一、概述1.异步时序2.亚稳态与建立保持时间 二、跨时钟域处理1.控制信号的跨时钟域处理(单bit数据)a.慢时钟域到快时钟域b.快时钟域到慢时钟域握手“扩宽”快时钟域脉冲时钟停止法窄脉冲捕捉电路 2.数据信号的跨时钟域处理(多bit数据…...
【Linux】创建分区后没有识别到分区盘?
如果在使用fdisk创建分区后明明输入p可以看到新建分区,但是lsblk查看的时候没有该分区,系统可能没有识别,你需要手动重新加载一下分区。 partprobe命令 partprobe命令用于重读分区表,将磁盘分区表变化信息通知内核,请求…...
W6100-EVB-PICO做DNS Client进行域名解析(四)
前言 在上一章节中我们用W6100-EVB-PICO通过dhcp获取ip地址(网关,子网掩码,dns服务器)等信息,给我们的开发板配置网络信息,成功的接入网络中,那么本章将教大家如何让我们的开发板进行DNS域名解…...
{Fixed} Android TV国内开机不会自动连接WIFI / 连接国内网络不会更新时间
引用: 悟空百科 使用usb adb、网络adb、串口敲以下命令修改安卓全局数据库 1、写入新的ntp服务器地址 adb shell settings put global ntp_server ntp.ntsc.ac.cn2、打开网络验 //如果你是Android R 以上的电视盒子 adb shell settings put global captive_portal_mode 1/…...
【ASP.NET MVC】数据到客户端(7)
前文ViewBag数据在服务端动态生成页面,也可以传到客户端浏览器供JS使用。 一、数据从控制器到客户端 前文介绍,动态生成页面时,控制器的数据 并没有传递到 客户端,而是给自己来用,生成View 再利用http传递到客户端浏…...
InnoDB有哪些特性
事务支持:InnoDB支持ACID(原子性、一致性、隔离性和持久性)事务,可以保证数据的完整性和一致性。它使用多版本并发控制(MVCC)来实现事务的隔离性,支持读已提交和可重复读两种隔离级别。 行级锁…...
【linux--->数据链路层协议】
文章目录 [TOC](文章目录) 一、数据链路层协议概念二、以太网帧格式1.字段分析 三、局域网通信原理四、ARP协议1.结构2.作用3.ARP通信过程4.ARP协议相关命令 五、局域网内中间人原理六、DNS系统(域名系统)1.域名概念2.DNS系统组成3.DNS协议3.浏览器输入域名后的通信过程4.dig工…...
如何在pytest接口自动化框架中扩展JSON数据解析功能?
开篇 上期内容简单说到了。params类类型参数的解析方法。相较于简单。本期内容就json格式的数据解析,来进行阐述。 在MeterSphere中,有两种方式可以进行json格式的数据维护。一种是使用他们自带的JsonSchema来填写key-value表单。另一种就是手写json。…...
哪些年,我们编程四处找的环境依赖
基于Maven,快速构建SSM项目 <properties><!-- 将spring和有关的升级版本,设置为5.0.5--><spring.version>5.0.5.RELEASE</spring.version><!-- 将mybatis和有关的升级版本,设置为3.1.1--><my…...
物联网工程开发实施,应该怎么做?
我这里刚好有嵌入式、单片机、plc的资料需要可以私我或在评论区扣个6 物联网工程的概念 物联网工程是研究物联网系统的规划、设计、实施、管理与维护的工程科学,要求物联网工程技术人员根 据既定的目标,依照国家、行业或企业规范,制定物联网…...
mysql使用SUBSTRING_INDEX拆分字符串,获取省、市、县和详细现住址
mysql使用SUBSTRING_INDEX拆分字符串,获取省、市、县和详细现住址 一、如何把"江西-上饶市-广丰县-大南镇古村村张家82号"拆分为省、市、县和详细现住址二、mysql的解决办法 一、如何把"江西-上饶市-广丰县-大南镇古村村张家82号"拆分为省、市、…...
Kubernetes中的就绪(readinessProbe)和存活(livenessProbe)探针
目录 案例一 案例二 readinessProbe就绪探针 readinessProbe就绪探针的作用 livenessProbe存活探针 livenessProbe存活探针的作用 探针的几种类型 探针的几个参数...
docker端口映射详解(随机端口、指定IP端口、随意ip指定端口、指定ip随机端口)
目录 docker端口映射详解 一、端口映射概述: 二、案例实验: 1、-P选项,随机端口 2、使用-p可以指定要映射到的本地端口。 Local_Port:Container_Port,任意地址的指定端口 Local_IP:Local_Port:Container_Port 映射到指定地…...
俄罗斯方块
俄罗斯方块简单实现 使用 pygame 模块实现俄罗斯方块的简单实现,这里没有使用pygame 自带的碰撞检测,而是自定义的方法实现边界碰撞和方块间碰撞检测。 代码实现 import random import pygame import time # 初始化游戏 pygame.init()# 设置游戏窗口大…...
web服务
静态网页与动态网页的区别 在网站设计中,静态网页是网站建设的基础,纯粹 HTML 格式的网页通常被称为“静态网页”,静态网页是标准的 HTML 文件,它的文件扩展名是 .htm、.html,可以包含文本、图像、声音、FLASH 动画、…...
【Rust 基础篇】Rust类型别名:为类型赋予新的名字
导言 Rust是一种以安全性和高效性著称的系统级编程语言,其设计哲学是在不损失性能的前提下,保障代码的内存安全和线程安全。在Rust中,类型别名是一种常见的编程特性,它允许为现有类型赋予新的名字,从而提高代码的可读…...
【机器学习】 贝叶斯理论的变分推理
许志永 一、说明 贝叶斯原理,站在概率角度上似乎容易解释,但站在函数立场上就不那么容易了;然而,在高端数学模型中,必须要在函数和集合立场上有一套完整的概念,其迭代和运算才能有坚定的理论基础。 二、贝叶…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
沙箱虚拟化技术虚拟机容器之间的关系详解
问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...
【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
一、前言 在HarmonyOS 5的应用开发模型中,featureAbility是旧版FA模型(Feature Ability)的用法,Stage模型已采用全新的应用架构,推荐使用组件化的上下文获取方式,而非依赖featureAbility。 FA大概是API7之…...
