【Linux】线程池的简易实现(懒汉模式)

文章目录

  • 前言
  • 一、懒汉方式
    • 1.普通模式
    • 1.线程安全模式
  • 二、源代码
    • 1.Task.hpp(要执行的任务)
    • 2.ThreadPool.hpp(线程池)
    • 3.Main.cpp


前言

`

线程池:
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

  • 线程池的应用场景:
    1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技 术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个 Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
    1. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
    1. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情 况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限, 出现错误.

一、懒汉方式

1.普通模式

懒汉方式实现单例模式

template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};

1.线程安全模式

存在一个严重的问题, 线程不安全.
第一次调用 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;
}
};注意事项:
1. 加锁解锁的位置
2. 双重 if 判定, 避免不必要的锁竞争
3. volatile关键字防止过度优化

二、源代码

1.Task.hpp(要执行的任务)

可根据自己的需求修改要执行的任务,这里我们以进行两个数的加减乘除为例子

#pragma once
#include <iostream>
#include <string>
std::string opers = "+-*/%";//运算符号enum//返回值
{DivZero = 1,ModZero,Unknown
};class Task
{
public:Task(){}Task(int x, int y, char op) : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0){}void run(){switch (oper_){case '+':result_ = data1_ + data2_;break;case '-':result_ = data1_ - data2_;break;case '*':result_ = data1_ * data2_;break;case '/':{if (data2_ == 0)exitcode_ = DivZero;elseresult_ = data1_ / data2_;}break;case '%':{if (data2_ == 0)exitcode_ = ModZero;elseresult_ = data1_ % data2_;}break;default:exitcode_ = Unknown;break;}}void operator()()//直接使用仿函数,调用run方法{run();}std::string GetResult(){std::string r = std::to_string(data1_);r += oper_;r += std::to_string(data2_);r += "=";r += std::to_string(result_);r += "[code: ";r += std::to_string(exitcode_);r += "]";return r;}std::string GetTask(){std::string r = std::to_string(data1_);r += oper_;r += std::to_string(data2_);r += "=?";return r;}~Task(){}private:int data1_;int data2_;char oper_;int result_;int exitcode_;
};

2.ThreadPool.hpp(线程池)

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>struct ThreadInfo//线程信息
{pthread_t tid;std::string name;
};static const int defalutnum = 5;//默认创建线程个数template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for (const auto &ti : threads_){if (ti.tid == tid)return ti.name;}return "None";}public:static void *HandlerTask(void *args)//这里需要用静态函数,因为在类中默认隐藏一个this指针{//这里传args直接把ThreadPool这个类传入进来进行操作//这样既能访问类中的方法也能符合传入的参数个数为1个的规则ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty())//循环判断,防止线程的伪唤醒{tp->ThreadSleep();}T t = tp->Pop();tp->Unlock();t();//直接调用任务的处理方法(仿函数)std::cout << name << " run, "<< "result: " << t.GetResult() << std::endl;}}void Start()//启动线程池{int num = threads_.size();for (int i = 0; i < num; i++){//创建线程并且给线程池命名threads_[i].name = "thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t = tasks_.front();tasks_.pop();return t;}void Push(const T &t){Lock();tasks_.push(t);Wakeup();//队列中有了数据//可能在之前任务为空的时候有线程进入了休眠//所以放入任务后,进行一次唤醒操作Unlock();}static ThreadPool<T> *GetInstance(){if (nullptr == tp_) // 双重判定空指针, 降低锁冲突的概率, 提高性能.{pthread_mutex_lock(&lock_);//这个lock_锁为静态成员if (nullptr == tp_){std::cout << "log: singleton create done first!" << std::endl;tp_ = new ThreadPool<T>();}pthread_mutex_unlock(&lock_);}return tp_;}private:
//因为为懒汉单例模式,只有一个ThreadPool类,所以为了避免
//冲突,把构造函数,拷贝构造函数都设置成私有禁用ThreadPool(int num = defalutnum) : threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}ThreadPool(const ThreadPool<T> &) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPool<T> *tp_;//懒汉单例模式所创建的//唯一的ThreadPool类static pthread_mutex_t lock_;//静态方法只能访问类中的静态成员,所以还需要一个静态锁在//获取这个对象的时候使用(GetInstance)
};//静态成员在类外进行初始化
template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

3.Main.cpp

#include <iostream>
#include <ctime>
#include "ThreadPool.hpp"
#include "Task.hpp"pthread_spinlock_t slock;int main()
{std::cout << "process runn..." << std::endl;sleep(3);//GetInstance()获取单例对象ThreadPool<Task>::GetInstance()->Start();srand(time(nullptr) ^ getpid());while(true){//1. 构建任务int x = rand() % 10 + 1;usleep(10);int y = rand() % 5;char op = opers[rand()%opers.size()];Task t(x, y, op);ThreadPool<Task>::GetInstance()->Push(t);//2. 交给线程池处理std::cout << "main thread make task: " << t.GetTask() << std::endl;sleep(1);}
}

在这里插入图片描述

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

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

相关文章

java之mybatis入门

大前题 正确创建好了springboot工程&#xff0c;极其依赖 配置数据库连接 application.yml spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/cangqiongusername: rootpassword: rootlombok Data 自动生成代码&#xff08…

【免费分享】概率神经网络的分类预测--基于PNN的变压器故障诊断

主要内容 ​《MATLAB神经网络43个案例分析》共有43章&#xff0c;内容涵盖常见的神经网络&#xff08;BP、RBF、SOM、Hopfield、Elman、LVQ、Kohonen、GRNN、NARX等&#xff09;以及相关智能算法&#xff08;SVM、决策树、随机森林、极限学习机等&#xff09;。同时&#x…

Java 对部分接口返回数据进行加密,或其他处理

业务场景&#xff1a;后端项目中分为PC端和移动端接口&#xff0c;移动端为例如 mobile 开头的URl&#xff0c;需求为调用移动端接口时&#xff0c;对返回数据进行加密&#xff0c;PC端不加密 import cn.hutool.core.date.DatePattern; import cn.hutool.json.JSONConfig; impo…

系统架构设计师-22年-下午题目

系统架构设计师-22年-下午题目 更多软考知识请访问 https://ruankao.blog.csdn.net/ 试题一必答&#xff0c;二、三、四、五题中任选两题作答 试题一 (25分) 说明 某电子商务公司拟升级其会员与促销管理系统&#xff0c;向用户提供个性化服务&#xff0c;提高用户的粘性。…

全流程机器视觉工程开发(三)任务前瞻 - 从opencv的安装编译说起,到图像增强和分割

前言 最近开始做这个裂缝识别的任务了&#xff0c;大大小小的问题我已经摸得差不多了&#xff0c;然后关于识别任务和分割任务我现在也弄的差不多了。 现在开始做正式的业务&#xff0c;也就是我们说的裂缝识别的任务。作为前言&#xff0c;先来说说场景&#xff1a; 现在相…

0131-2-关于事件捕获和冒泡

关于事件捕获和冒泡 DOM事件流分为三个阶段&#xff1a;捕获阶段、目标阶段、冒泡阶段 点击目标元素后&#xff0c;不会马上触发目标元素&#xff0c;而是先执行事件捕获&#xff0c;从顶部逐步到目标元素&#xff1b;处于目标阶段的时候触发目标元素&#xff1b;最后冒泡阶段…

如何在Raspberry Pi上启用SSH并结合cpolar内网穿透实现公网远程访问本地树莓派

文章目录 如何通过 SSH 连接到树莓派步骤1. 在 Raspberry Pi 上启用 SSH步骤2. 查找树莓派的 IP 地址步骤3. SSH 到你的树莓派步骤 4. 在任何地点访问家中的树莓派4.1 安装 Cpolar4.2 cpolar进行token认证4.3 配置cpolar服务开机自启动4.4 查看映射到公网的隧道地址4.5 ssh公网…

我的创作纪念日和前端碎碎念

机缘 作为一个前端开发者&#xff0c;我一直热衷于将设计和技术相结合&#xff0c;尽可能提升用户体验。我最初成为创作者的初心源于学习记录&#xff0c;把创作当作一个笔记&#xff0c;希望把自己遇到的问题&#xff0c;以及学习到的实用技巧记录下来&#xff0c;方便学习回…

Qt Excel读写 - QXlsx的安装配置以及测试

Qt Excel读写 - QXlsx的安装配置以及测试 引言一、安装配置二、简单测试 引言 Qt无自带的库处理Excel 文件&#xff0c;但可通过QAxObject 借助COM接口进行Excel的读写1。亦可使用免费的开源第三方库&#xff1a;QXlsx&#xff0c;一个基于Qt库开发的用于读写Microsoft Excel文…

CUDA下载安装教程,新手详细

目录 一、下载二、安装三、 设置环境变量四、补丁安装 由于项目需要安装特定版本的CUDA&#xff0c;现记录安装过程。 一、下载 进入官方下载地址&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive 选择自己需要的版本。如果没有明确要求版本号&#xff0c;那么…

安卓视图基础

目录 设置视图的宽高 设置视图的间隔 设置视图的对齐方式 设置视图的宽高 设置视图的间隔 设置视图的对齐方式 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"a…

聊聊java中的Eureka和Nacos

本文主要来自于黑马课程中 1.提供者与消费者 在服务调用关系中&#xff0c;会有两个不同的角色&#xff1a; 服务提供者&#xff1a;一次业务中&#xff0c;被其它微服务调用的服务。&#xff08;提供接口给其它微服务&#xff09; 服务消费者&#xff1a;一次业务中&#xff0…

javaweb项目,springboot幼儿园健康管理系统,界面美观,增删改查。

javaweb项目&#xff0c;幼儿园健康管理系统&#xff0c;界面美观&#xff0c;增删改查。 管理员&#xff0c;老师&#xff0c;学生三个角色。 功能&#xff1a;权限管理&#xff0c;咨询列表&#xff0c;教师列表&#xff0c;班级列表&#xff0c;健康档案列表&#xff0c;评…

python数据类型-列表

1 python中列表的定义 python中列表是一种有序和可更改的集合&#xff0c;允许重复的成员&#xff0c;列表中的元素之间数据类型可以不同&#xff08;元素之间数据类型可以不相同&#xff0c;这一点和其它的面向对象的开发语言有很大的不同&#xff0c;如C#、Java&#xff09;…

【Django自学】Django入门:如何使用django开发一个web项目(非常详细)

测试机器&#xff1a;windows11 x64 python版本&#xff1a;3.11 一、安装Django 安装步骤非常简单&#xff0c;使用pip安装就行 pip install django安装完成之后&#xff0c;python的 Scripts 文件夹下&#xff0c;会多一个 django-admin.exe (管理创建django项目的工具)。…

【鸿蒙】大模型对话应用(二):对话界面设计与实现

Demo介绍 本demo对接阿里云和百度的大模型API&#xff0c;实现一个简单的对话应用。 DecEco Studio版本&#xff1a;DevEco Studio 3.1.1 Release HarmonyOS SDK版本&#xff1a;API9 关键点&#xff1a;ArkTS、ArkUI、UIAbility、网络http请求、列表布局、层叠布局 对话页…

springboot整合mqtt实现消息订阅和推送

前言 mica-mqtt-client-spring-boot-starter是一个基于Spring Boot的MQTT客户端启动器&#xff0c;它集成了mica-mqtt客户端&#xff0c;提供了在Spring Boot应用程序中使用MQTT协议进行消息通信的能力。以下是关于mica-mqtt-client-spring-boot-starter的简介&#xff1a; 特…

C++ fill()函数最详细介绍

文章目录 函数参数介绍函数功能函数使用注意点使用例子1.将数组arr[5]所有元素初始化为02.字符数组初始化3.vector对象 fill函数是C标准库中的一个算法函数&#xff0c;用于将指定范围内的元素赋值为给定的值。 函数参数介绍 fill( first, last, value );它接受三个参数&#…

idea激活教程(2020.1.4及以上版本)

首先点击试用版本&#xff0c;进入软件&#xff0c;再依次进行一下操作 一、在idea的Plugins配置中添加Z大的插件市场 上图中加载出来的插件是默认的&#xff0c;大家不用在意&#xff0c;直接点击“Manage Plugin Repositoryies…”打开配置弹窗 点击号&#xff0c;添加一行…

Qt 基础之QDataTime

Qt 基础之QDataTime 引言一、获取(设定)日期和时间二、时间戳三、时间计算 (重载运算符) 引言 QDataTime是Qt框架中用于处理日期和时间的类。它提供了操作和格式化日期、时间和日期时间组合的功能。QDataTime可以用于存储和检索日期和时间、比较日期和时间、对日期和时间执行算…