线程池|单例模式|STL、智能指针线程安全|读者写者问题
线程池
线程池的逻辑思想:
每当我们处理一个任务就要创建一个线程,创建线程的开销是很大的。因此我们可以预先创建一批线程,任务队列里没有任务的时候,每个线程都休眠,当队里中有任务的时候,就可以唤醒线程进行处理。唤醒线程的成本比创建整个线程的成本小,这就是线程池的逻辑思想。
线程池的概念:
线程池: 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池常见的应用场景:
需要大量的线程来完成任务,且完成任务的时间比较短。
对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
代码实现
首先我们先熟悉一下整体的框架:

Thread.hpp
#pragma once
#include <pthread.h>
#include <iostream>
#include <cassert>
#include <string>
#include <functional>
namespace ThreadNs
{typedef std::function<void*(void*)> func_t;const int num =1024;class Thread{private:static void* start_routine(void*args){Thread* _this = static_cast<Thread*>(args);return _this->callback();}public:Thread(){char namebuffer[num];snprintf(namebuffer,sizeof namebuffer,"thread-%d",threadnum++);name_ = namebuffer;}void start(func_t func,void*args = nullptr){func_ = func;args_ = args;int n = pthread_create(&tid_,nullptr,start_routine,this);assert(n==0);(void)n;}void join(){int n = pthread_join(tid_,nullptr);assert(n==0);(void)n;}std::string threadname(){return name_;}~Thread(){}void* callback(){return func_(args_);}private:std::string name_;func_t func_;void *args_;pthread_t tid_;static int threadnum;};int Thread::threadnum = 1;
}
LockGuard.hpp
#include <iostream>
#include <mutex>
class Mutex
{
public:Mutex(pthread_mutex_t*lock_p=nullptr):lock_p_(lock_p){}void lock(){if(lock_p_) pthread_mutex_lock(lock_p_);}void unlock(){if(lock_p_) pthread_mutex_unlock(lock_p_);}~Mutex(){}
private:pthread_mutex_t * lock_p_;
};
class LockGuard
{
public:LockGuard(pthread_mutex_t*mutex):mutex_(mutex){mutex_.lock();}~LockGuard(){mutex_.unlock();}
private:Mutex mutex_;
};
ThreadPool.hpp
#pragma once
#include "Thread.hpp"
#include "LockGuard.hpp"
#include <vector>
#include <queue>
#include <pthread.h>
#include <mutex>
#include <unistd.h>
using namespace ThreadNs;
const int gnum = 3;
template <class T>
class ThreadPool;template <class T>
class ThreadData
{
public:ThreadPool<T> *threadpool;std::string name;
public:ThreadData(ThreadPool<T> *tp, const std::string &n) : threadpool(tp), name(n){ }
};
template <class T>
class ThreadPool
{
private:static void *handlerTask(void *args){ThreadData<T> *td = (ThreadData<T> *)args;ThreadPool<T> *threadpool = static_cast<ThreadPool<T> *>(args);while (true){T t;{LockGuard lockguard(td->threadpool->mutex());while(td->threadpool->isQueueEmpty()){td->threadpool->threadWait();}t = td->threadpool->pop(); }std::cout << td->name << " 获取了一个任务" << t.toTaskString() << "并处理完成,结果是: " << t() << std::endl;}delete td;return nullptr;}
public:void lockQueue() { pthread_mutex_lock(&_mutex); }void unlockQueue() { pthread_mutex_unlock(&_mutex); }bool isQueueEmpty() { return _task_queue.empty(); }void threadWait() { pthread_cond_wait(&_cond, &_mutex); }T pop(){T t = _task_queue.front();_task_queue.pop();return t;}void Push(const T &in){LockGuard lockguard(&_mutex);_task_queue.push(in);pthread_cond_signal(&_cond);}pthread_mutex_t *mutex(){return &_mutex;}public:ThreadPool(const int &num = gnum) : _num(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);for (int i = 0; i < _num; i++){_threads.push_back(new Thread());}}void run(){for (const auto &t : _threads){ThreadData<T> *td = new ThreadData<T>(this, t->threadname());t->start(handlerTask, td);std::cout << t->threadname() << "start..." << std::endl;}}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);for (const auto &t : _threads)delete t;}
private:int _num;std::vector<Thread *> _threads;std::queue<T> _task_queue;pthread_mutex_t _mutex;pthread_cond_t _cond;
};
我们将线程池进行了模板化,因此线程池当中存储的任务类型可以是任意的;现在我们想像之前处理各种数据的计算,那么先引入任务组件Task.hpp:
#pragma once
#include <iostream>
#include <functional>
class Task
{using func_t = std::function<int(int,int ,char)>;
public:Task(){}Task(int x,int y,char op,func_t func):_x(x),_y(y),_op(op),_callback(func){}std::string operator()(){int result = _callback(_x,_y,_op);char buffer[1024];snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);return buffer;}std::string toTaskString(){char buffer[1024];snprintf(buffer,sizeof buffer,"%d %c %d = ?",_x,_op,_y);return buffer;}
private:int _x;int _y;char _op;func_t _callback;
};const std::string oper = "+-*/%";
int mymath(int x,int y,char op)
{int result = 0;switch(op){case '+':result = x+y;break;case '-':result = x-y;break;case '*':result = x*y;break;case '/':if(y==0){std::cerr<<"div zero error!"<<std::endl;result = -1;}else{result = x/y;}break;case '%':if(y==0){std::cerr<<"mod zero error!"<<std::endl;result = -1;}else{result = x%y;}break;default:break;}return result;
}
main.cc
#include "ThreadPool.hpp"
#include "Thread.hpp"
#include "Task.hpp"
#include <unistd.h>
#include <ctime>
int main()
{ThreadPool<Task>* tp = new ThreadPool<Task>();tp->run();srand(time(0));int x,y;char op;while(true){x = rand()%10+1;y = rand()%20+1;op =oper[rand()%oper.size()];Task t(x,y,op,mymath);tp->Push(t);sleep(1);}return 0;
}
单例模式
单例模式是一种创建型设计模式,它保证一个类只有一个实例存在,并且提供一个全局访问点来访问该实例。单例模式有饿汉模式和懒汉模式。
洗完的例子:
吃完饭 , 立刻洗碗 , 这种就是 饿汉方式 . 因为下一顿吃的时候可以立刻拿着碗就能吃饭 .吃完饭 , 先把碗放下 , 然后下一顿饭用到这个碗了再洗碗 , 就是 懒汉方式 .
饿汉实现方式和懒汉实现方式
饿汉方式实现单例模式
template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};
懒汉方式实现单例模式
template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};
这样的懒汉设计可能会导致线程不安全,第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例。因此以下才是安全版的懒汉单例模式:
// 懒汉模式 , 线程安全template <typename T>class Singleton {volatile static T* inst; // 需要设置 volatile 关键字 , 否则可能被编译器优化 .static std::mutex lock;public:static T* GetInstance(){if (inst == NULL) { // 双重判定空指针 , 降低锁冲突的概率 , 提高性能 .lock.lock(); // 使用互斥锁 , 保证多线程情况下也只调用一次 new.if (inst == NULL) {inst = new T();}lock.unlock();}return inst;}};
线程池的单例模式
我们要做的第一步就是把构造函数私有,再把拷贝构造和赋值运算符重载delete:

接下来就要在成员变量中定义一个静态指针,方便获取单例对象:

在设置获取单例对象的函数的时候,注意要设置成静态成员函数,因为在获取对象前根本没有对象,无法调用非静态成员函数(无this指针):

主函数的调用:

不过也许会出现多个线程同时申请资源的场景,所以还需要一把锁来保护这块资源,而这把锁也得设置成静态,因为GetSingle()函数是静态的:

STL,智能指针和线程安全
STL 的设计初衷是将性能挖掘到极致 , 而一旦涉及到加锁保证线程安全 , 会对性能造成巨大的影响 . 而且对于不同的容器, 加锁方式的不同 , 性能可能也不同 ( 例如 hash 表的锁表和锁桶 ).因此 STL 默认不是线程安全 . 如果需要在多线程环境下使用 , 往往需要调用者自行保证线程安全 .
对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.
其他常见的各种锁
悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试
自旋锁:使用自旋锁时,当多线程发生竞争锁的情况时,加锁失败的线程会忙等待(这里的忙等待可以用 while 循环等待实现),直到它拿到锁。而互斥锁加锁失败后,线程会让出 CPU 资源给其他线程使用,然后该线程会被阻塞挂起。如果成功申请临界资源的线程,临界区代码执行时间过长,自旋的线程会长时间占用 CPU 资源,所以自旋的时间和临界区代码执行的时间是成正比的关系。如果临界区代码执行的时间很短,就不应该使用互斥锁,而应该选用自旋锁。因为互斥锁加锁失败,是需要发生上下文切换的,如果临界区执行的时间比较短,那可能上下文切换的时间会比临界区代码执行的时间还要长。
读者和写者问题:

读者写者问题和生产者消费者模型的本质区别就是消费者会取走数据,而读者不会取走数据。
读写锁接口
//初始化读写锁
pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
//销毁读写锁
pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//读加锁
pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//写加锁
pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
//解锁
pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
相关文章:
线程池|单例模式|STL、智能指针线程安全|读者写者问题
线程池 线程池的逻辑思想: 每当我们处理一个任务就要创建一个线程,创建线程的开销是很大的。因此我们可以预先创建一批线程,任务队列里没有任务的时候,每个线程都休眠,当队里中有任务的时候,就可以唤醒线程…...
Linux的基本使用和Web程序部署(JavaEE初阶系列18)
目录 前言: 1.Linux 1.1Linux是什么 1.2Linux发行版 1.3Linux环境搭建 1.3.1环境搭建方式 1.3.2使用云服务器 1.4使用终端软件连接到Linux 1.4.1什么是终端软件 1.4.2使用Xshell登录主机 1.5Linux常用的命令 1.5.1ls 1.5.2cd 1.5.3pwd 1.5.4touch 1.…...
EXCEL 中find,if and,if or
接上一篇sql中find函数的作用,由于工作需求是用帆软做报表,他的一些代码不仅有js,sql中的还有一些excel的相关知识,故作整理。 FIND() excel中的find原理和sql中相似,具体可查看 SQL函数 $FIND_Yangshiwei....的博客…...
中国环球电视网-学英文必备
CGTN全称是China Global Television Network,即“中国环球电视网”,这是一个以播送新闻、评论、访谈节目为主的电视频道,CGTN的前身即央视的全英文频道CCTV-9/CCTV News。CGTN每天的节目内容很丰富,包括全球新闻(the w…...
【web开发】4.JavaScript与jQuery
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、JavaScript与jQuery二、JavaScript常用的基本功能1.插入位置2.注释3.变量4.数组5.滚动字符 三、jQuery常用的基本功能1.引入jQuery2.寻找标签3.val、text、appe…...
UNI-APP 框架中解决打包后index.html文件中没有引号问题
问题 打包后index.html文件中src属性没有引号。打包后修改默认相对路径为“./”,比如index.xxxx.js中的a.p属性值希望为“./”,默认为“/”。 问题一 在项目根目录下添加文件vue.config.js。在文件中添加如下内容: module.exports {chai…...
借助AI分析哥斯拉木马原理与Tomcat回显链路挖掘
前言 本次分析使用了ChatGPT进行辅助分析,大大提升了工作效率,很快就分析出木马的工作流程和构造出利用方式。 分析 首先对该木马进行格式化,以增强代码的可读性。得到如下代码 <jsp:root xmlns:jsp"http://java.sun.com/JSP/Page" vers…...
Java进行多线程编程?(lambda表达式~)
本文标题:Java进行多线程编程?那么,Java为啥不学学如何进程多进程编程呢??原因在于:Java圈子中不提倡多进程编程~~ 接下来,我们来写一个最为基础/入门的HelloWord程序来感受如何进行多线程~~ J…...
MySQL中的索引事务(2)事务----》数据库运行的原理知识+面试题~
本篇文章建议读者结合:MySQL中的索引事务(1)索引----》数据库运行的原理知识面试题~_念君思宁的博客-CSDN博客此时,如果你根据name来查询,查到叶子节点得到的只是主键id,还需要通过主键id去主键的B树里面在…...
【数据结构】 七大排序详解(贰)——冒泡排序、快速排序、归并排序
文章目录 ⚽冒泡排序⚾算法步骤🎨算法优化🥎代码实现:🏀冒泡排序的特性总结 🧭快速排序⚽算法思路📌思路一(Hoare版)📌思路二(挖坑法)Ὄ…...
小程序的使用
微信小程序开发 外部链接别人的总结查看(超详细保姆式教程) 基础语法 1.数据绑定 1.1 初始化数据 页面.js的data选项中Page({data: {motto: Hello World,id:18} })使用数据 单项数据流:Mustache 语法 a)模板结构中使用双大括号 {{data}} …...
Spring整合tomcat的WebSocket详细逻辑(图解)
主要解决存在的疑问 为什么存在2种spring整合websocket的方式,一种是使用ServerEndpoint注解的方式,一种是使用EnableWebSocket注解的方式,这2种有什么区别和联系?可以共存吗?它们实现的原理是什么?它们的各…...
【笔试强训选择题】Day37.习题(错题)解析
作者简介:大家好,我是未央; 博客首页:未央.303 系列专栏:笔试强训选择题 每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!! 文章目录 前言一、Day…...
如何使用HTTP代理爬虫,防止对网站造成负面影响
在当今大数据时代,爬虫技术已经成为了获取数据的重要手段之一。但是,由于爬虫程序的高频访问容易对目标网站造成负面影响,如增加服务器负载、影响网站性能等,因此,如何使用HTTP代理爬虫防止对网站造成负面影响成为了一…...
磐基2.0搭建es集群
参考: k8s安装elasticsearch集群 k8s安装elasticsearch集群_k8s部署elasticsearch集群_MasonYyp的博客-CSDN博客1 环境简述搭建es集群需要使用的技术如下:k8s集群、StatefulSet控制器、Service(NodePort)服务、PV、PVC、volumeC…...
Java中IO类扫盲篇
文章目录 一、简介二、字节流与字符流1. 字节流(InputStream、OutputStream)介绍与用法2. 字符流(Reader、Writer)介绍与用法 三、文件操作与目录遍历1. File类的基本使用2. 目录遍历与递归操作 四、序列化与反序列化1. 序列化与反…...
中秋国庆双节将至,企业如何进行软文推广?
节点营销是每个企业都会面临的课题,中秋国庆双节将至,这两个节日不仅是人们消费的高峰期,也是各大企业通过节日营销提高品牌知名度和美誉度的最佳时机,节点营销的方式之一就是软文推广,那么企业应该如何利用双节来进行…...
SpringMvc--CRUD
目录 一.什么是SpringMvc--CRUD 二.前期准备 公共页面跳转(专门用来处理页面跳转) 三.ssm之CRUD后端实现 配置pom.xml 双击mybatis-generator:generate自动生成mapper 编写generatorConfig.xml 项目结构 编写PagerAspect切面类 编写hpjyBiz接口类 编写hpjyBizImpl接…...
数据库去重(MYSQL和ORACLE)
一、数据库中的去重操作(删除数据库中重复记录的SQL语句)主要有三种方法 (1)、rowid方法 (2)、group by 方法 (3)、distinct方法 1、用rowid方法 根据Oracle带的rowid属性&#…...
微服务-kubernetes安装
文章目录 一、前言二、kubernetes2.1、Kubernetes (K8S) 是什么2.1.1、主要特性:2.2.2、传统部署方式:2.2.3、虚拟机部署2.2.4容器部署2.2.5什么时候需要 Kubernetes2.2.6、Kubernetes 集群架构 三、kubernetes安装3.1、主节点需要组件3.1.1、设置对应主…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
