【Linux】:线程池

朋友们、伙计们,我们又见面了,本期来给大家带来线程池相关的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

​ 

目录

1. 线程池

1.1 预备工作

1.2 模块设计

2. 基础框架

2.1 测试版接口

2.2.1 Main.cc测试

3. 引入任务

3.1 执行任务(ThreadRun)

3.2 添加任务

3.3 Main.cc测试

4. 接入日志功能

5. 设计单例


1. 线程池

我们之前要用到线程的地方都是当有任务来的时候我们才创建线程,就会导致效率比较低下,所以我们想实现一个线程池,预先提现创建好一批线程,然后在线程池中会有一个任务队列,我们也可以向任务队列中添加任务,然后我们预先创建好的线程就开始依次从任务队列中获取任务并执行,这样子效率就高不少了;

1.1 预备工作

我们的线程池因为是多线程,所以必须要有锁,所以我们先将锁进行简单的封装:

LockGuard.hpp:

#pragma once#include <pthread.h>// 不定义锁,默认认为外部会给我们传入锁对象
class Mutex
{
public:Mutex(pthread_mutex_t *lock):_lock(lock){}void Lock(){pthread_mutex_lock(_lock);}void Unlock(){pthread_mutex_unlock(_lock);}~Mutex(){}private:pthread_mutex_t *_lock;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *lock): _mutex(lock){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}
private:Mutex _mutex;
};

然后我们线程池也需要用到线程,所以我们使用之前封装好的线程(Thread.hpp),同时我们还想引入一下日志功能(Log.hpp);

关于日志的详细实现请移步至:负载均衡在线OJ里面有对日志的介绍,这里将其复用一下,稍作修改;

1.2 模块设计

我们实现线程池使用分模块编写,每个文件实现对应的功能,让代码不会冗余在一起;

  • ThreadPool.hpp:实现线程池的主要逻辑
  • Thread.hpp:封装原生线程库
  • LockGuard.hpp:封装互斥锁
  • Log.hpp:日志功能
  • Main.cc:对线程池进行测试
  • Makefile:自动化构建代码

2. 基础框架

  • 我们的线程池中需要有一批线程,所以我们使用vector来管理这批线程,还可以设置线程池中需要有多少个线程;
  • 然后需要有一个储存任务的队列、锁、条件变量;
  • 我们使用模版编程来实现自定义任务类型;
  • 初步实现的功能就是一个启动线程池和一个向线程池中push任务。

2.1 测试版接口

  • 我们想有一个运行任务的接口(ThreadRun),然后还想要一个等待线程的接口(Wait);
  • 我们要运行线程池就先需要创建一批线程,这个工作我们在构造函数中实现;
  • 有了线程之后,我们要启动线程就遍历线程池,然后一次调用他们的启动接口即可;
  • 为了先进行测试,我们的ThreadRun接口就先写一段测试代码;
  • 等待线程的接口同样也是遍历然后调用join;

2.2.1 Main.cc测试

这样子已经实现了一个非常简易版的线程池了;

3. 引入任务

上面实现的代码是用来测试基本逻辑的,我们的任务队列中是没有任务的,所以接下来我们需要引入一批任务:

Task.hpp:

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>const int defaultvalue = 0;enum
{ok = 0,div_zero,mod_zero,unknow
};const std::string opers = "+-*/%";class Task
{
public:Task(){}Task(int x, int y, char op): data_x(x), data_y(y), oper(op), result(defaultvalue), code(ok){}void Run(){switch (oper){case '+':result = data_x + data_y;break;case '-':result = data_x - data_y;break;case '*':result = data_x * data_y;break;case '/':{if (data_y == 0)code = div_zero;elseresult = data_x / data_y;}break;case '%':{if (data_y == 0)code = mod_zero;elseresult = data_x % data_y;}break;default:code = unknow;break;}}void operator()(){Run();}std::string PrintTask(){std::string s;s = std::to_string(data_x);s += oper;s += std::to_string(data_y);s += "=?";return s;}std::string PrintResult(){std::string s;s = std::to_string(data_x);s += oper;s += std::to_string(data_y);s += "=";s += std::to_string(result);s += " [";s += std::to_string(code);s += "]";return s;}~Task(){}private:int data_x;int data_y;char oper; // + - * / %int result;int code; // 结果码,0: 结果可信 !0: 结果不可信,1,2,3,4
};

关于任务设计大家也可以自己设计,不想设计的直接拷贝即可,这里的任务就是对数据进行四种运算;

3.1 执行任务(ThreadRun)

有了任务之后,我们想要执行我们的任务,在执行之前我们想要实现两个接口,一个是任务队列没有任务时,线程就一直等待,另一个是有了任务就将线程进行唤醒并执行任务;

在执行任务接口中首先肯定需要取任务(在任务队列中取任务)如果任务队列中没有任务,就需要进行等待,取到任务之后就将任务分配给线程进行处理任务即可,取任务的这个过程是需要用锁进行保护的;

 

3.2 添加任务

添加任务也是需要用锁保护的,另外将任务添加到任务队列中时需要将线程唤醒去执行任务;

3.3 Main.cc测试

本次测试我们就使用我们引入的任务;

Main.cc:

#include <iostream>
#include <memory>
#include <ctime>#include "ThreadPool.hpp"
#include "Task.hpp"int main()
{std::unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>());tp->Start();srand((uint64_t)time(nullptr) ^ getpid());while (true){int x = rand() % 100 + 1;usleep(1234);int y = rand() % 200;usleep(1234);char oper = opers[rand() % opers.size()];Task t(x, y, oper);tp->Push(t);// std::cout << "make task: " << t.PrintTask() << std::endl;sleep(1);}tp->Wait();return 0;
}

4. 接入日志功能

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>
#include "Thread.hpp"
#include "LockGuard.hpp"
#include "Log.hpp"
#include "Task.hpp"using namespace ns_log;
static const int defaultnum = 5;// 线程数据
class ThreadData
{
public:ThreadData(const std::string &name) : threadname(name){}~ThreadData(){}public:std::string threadname;
};template <class T>
class ThreadPool
{
public:ThreadPool(int num = defaultnum) : _thread_num(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);// 创建指定个数的线程for (int i = 0; i < _thread_num; i++){std::string threadname = "thread-";threadname += std::to_string(i + 1);ThreadData td(threadname);// 创建// Thread<ThreadData> t(threadname, std::bind(&ThreadPool<T>::ThreadRun, this, std::placeholders::_1), td);// _threads.push_back(t);// 两种写法都可行,这种写法可以减少拷贝_threads.emplace_back(threadname, std::bind(&ThreadPool<T>::ThreadRun, this, std::placeholders::_1), td);LOG(INFO) << td.threadname << " is created..." << "\n";}}// 启动线程池bool Start(){for (auto &thread : _threads){thread.Start();LOG(INFO) << thread.ThreadName() << " is running..." << "\n";}return true;}void ThreadWait() // 等待{pthread_cond_wait(&_cond, &_mutex);}void ThreadWakeup() // 唤醒{pthread_cond_signal(&_cond);}// 执行任务void ThreadRun(ThreadData &td){while (true){T t;// 取任务{// 加锁LockGuard lockguard(&_mutex);while (_q.empty()){// 没有任务时就等待ThreadWait();}t = _q.front();_q.pop();}// 处理任务t();LOG(DEBUG) << td.threadname << " handler task " << t.PrintTask() << " ,result is: " << t.PrintResult() << "\n";}}// 添加任务void Push(const T &in){// 加锁LockGuard lockguard(&_mutex);// 添加_q.push(in);// 唤醒ThreadWakeup();}// 等待线程void Wait(){for (auto &thread : _threads){thread.Join();}}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}private:ThreadPool(const ThreadPool<T> &tp) = delete;const ThreadPool<T> &operator=(const ThreadPool<T>) = delete;std::queue<T> _q;                         // 任务队列std::vector<Thread<ThreadData>> _threads; // 线程池int _thread_num;                          // 线程个数pthread_mutex_t _mutex;                   // 锁pthread_cond_t _cond;                     // 条件变量
};

5. 设计单例

除了上面实现的功能,我们也可以将我们的线程池设置成为单例模式,这里采用的是懒汉模式;

需要注意的是,因为是多线程,所以在获取单例的时候存在线程安全问题,如果同时有多个线程来获取单例,就会创建多个单例,所以也需要用到互斥锁;

所有源码:https://gitee.com/yue-sir-bit/linux/tree/master/ThreadPool

 

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

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

相关文章

SpringMVC (一)基础

目录 SpringMVC 一 简单使用 1 新建模块选择指定参数 2 创建实现类 3 将项目启动 4 运行结果&#xff1a;在浏览器当中响应执行 二 RequestMapping 三 请求限定 SpringMVC SpringMVC是Spring的web模块&#xff0c;用来开发Web应用&#xff0c;SpringMVC应用最终作为B/…

tomcat应用的作用以及安装,以及tomcat软件的开机自启动。

一.tomcat介绍 1.作用 tomcat是一款用来部署网站服务器的一款软件。 动态网站主流语言&#xff1a; PHP, lamp/lnmp平台 Java语言&#xff0c;运行在tomcat平台。【只要这个网站或者软件是Java语言写的&#xff0c;我们都可以在tomcat平台上去运行这个java程序。】 网站是…

CSDN博客:Markdown编辑语法教程总结教程(下)

❤个人主页&#xff1a;折枝寄北的博客 Markdown编辑语法教程总结 前言1. LaTex数学公式2. 插入不同类别的图2.1 插入甘特图2.2 插入UML图2.3 插入Mermaid流程图2.4 插入Flowchart流程图2.5 插入classDiagram类图 3. CSDN快捷键4. 字体相关设置4.1 字体样式改变4.2 字体大小改变…

AI模型的构建过程是怎样的(下)

你好,我是舒旻。 上节课,我们讲了一个模型构建的前 2 个环节,模型设计和特征工程。今天,我们继续来讲模型构建的其他 3 个环节,说说模型训练、模型验证和模型融合中,算法工程师的具体工作内容,以及 AI 产品经理需要掌握的重点。 模型训练 模型训练是通过不断训练、验证…

K邻近算法

K邻近算法 1 算法介绍 1.1 什么是K-NN K-NN&#xff08;K Near Neighbor&#xff09;&#xff1a;k个最近的邻居&#xff0c;即每个样本都可以用它最接近的k个邻居来代表。K-NN算法属于监督学习方式的分类算法&#xff0c;即计算某给点到每个点的距离作为相似度的反馈。简单…

晋升系列4:学习方法

每一个成功的人&#xff0c;都是从底层开始打怪&#xff0c;不断的总结经验&#xff0c;一步一步打上来的。在这个过程中需要坚持、总结方法论。 对一件事情长久坚持的人其实比较少&#xff0c;在坚持的人中&#xff0c;不断的总结优化的更少&#xff0c;所以最终达到高级别的…

LoRA,DoRA,RSLoRA,LoRA+ 是什么

LoRA,DoRA,RSLoRA,LoRA+ 是什么 一、LoRA(Low-Rank Adaptation,低秩适应) 核心原理:冻结预训练模型参数,仅在每层插入两个低秩矩阵(A∈R^{rd}, B∈R^{dr}),通过分解权重增量ΔW=BA近似全秩更新,参数量仅为全量微调的0.01%-1%。 举例:在GPT-2(774M参数)的注意力…

HTTP发送POST请求的两种方式

1、json String json HttpRequest.post(getUrl(method, "v1", url, userId, appKey)).header("Content-type", "application/json") // 设置请求头为 JSON 格式.body(JSONUtil.toJsonStr(params)) // 请求体为 JSON 字符串.execute().body(); …

TCP并发服务器

单循环服务器&#xff1a;服务器在同一时刻只能响应一个客户端的需求。 并发服务器&#xff1a;服务器在同一时刻可以响应多个客户端的需求。 构建TCP服务器的方法&#xff1a; IO多路复用的函数接口[select() poll() epoll()] 1.多进程实现TCP并发服务器 #include <s…

【大模型统一集成项目】如何封装多个大模型 API 调用

&#x1f31f; 在这系列文章中&#xff0c;我们将一起探索如何搭建一个支持大模型集成项目 NexLM 的开发过程&#xff0c;从 架构设计 到 代码实战&#xff0c;逐步搭建一个支持 多种大模型&#xff08;GPT-4、DeepSeek 等&#xff09; 的 一站式大模型集成与管理平台&#xff…

Linux基础开发工具—vim

目录 1、vim的概念 2、vim的常见模式 2.1 演示切换vim模式 3、vim命令模式常用操作 3.1 移动光标 3.2 删除文字 3.3 复制 3.4 替换 4、vim底行模式常用命令 4.1 查找字符 5、vim的配置文件 1、vim的概念 Vim全称是Vi IMproved&#xff0c;即说明它是Vi编辑器的增强…

数据结构与算法效率分析:时间复杂度与空间复杂度详解(C语言)

1. 算法效率 1.1 如何衡量一个算法的好坏&#xff1f; 在计算机程序设计中&#xff0c;衡量算法优劣的核心标准是效率。但效率不仅指运行速度&#xff0c;还需要综合以下因素&#xff1a; 时间因素&#xff1a;算法执行所需时间 空间因素&#xff1a;算法运行占用的内存空间…

使用arm嵌入式编译器+makefile编译管理keil项目

目录 # arm嵌入式编译器-知识 # arm嵌入式编译器-知识 --- arm嵌入式编译器&#xff08;百度云盘&#xff09;下载&#xff1a;arm嵌入式编译器 keil&#xff0c; 链接 提取码: 8a6c arm官方使用教程&#xff1a; Arm Compiler 6 User Guide linux 安装完了有个非常重要的一步…

SwiftUI学习笔记day1---Stanford lecture1

SwiftUI学习笔记day1—Stanford lecture1 课程链接&#xff1a;Lecture 1 | Stanford CS193p 2023课程大纲&#xff1a;代码仓库&#xff1a;github/iOS 文章目录 SwiftUI学习笔记day1---Stanford lecture11.在Xcode中创建一个swiftUI的工程2.简单认识Xcode这个IDE3.尝试理解示…

vanna+deepseekV3+streamlit本地化部署

文章目录 1、vanna介绍1.1、基本介绍1.2、工作原理1.3、优点 2、vannadeepseekV3mysqlstreamlit本地化部署2.1、创建conda环境&#xff0c;安装依赖2.2、Mysql数据准备2.3、新建pycharm项目2.4、封装deepseek大模型2.5、定义MyVanna2.6、构建streamlit的app2.7、app演示 1、van…

【LangChain接入阿里云百炼deepseek】

这是目录 前言阿里云百炼注册账号使用代码执行结果 前言 大模型爆火&#xff0c;现在很多教程在教怎么使用大模型来训练Agent智能体&#xff0c;但是大部分教程都是使用的OpenAI。 最近阿里云推出DeepSeek-R1满血版&#xff0c;新用户可享100万免费Token额度。 今天就教大家怎…

【优选算法】二分法(总结套路模板)

目录 1. 题目一 &#xff1a;二分查找 解题思路&#xff1a; 模板总结&#xff08;简单版&#xff0c;不适用所有情况&#xff09; 代码实现&#xff1a; 2. 题目二 解题思路&#xff1a; 模板总结&#xff08;几乎万能&#xff09; 代码实现&#xff1a; 3. 题目…

Qt开源控件库(qt-material-widgets)的编译及使用

项目简介 qt-material-widgets是一个基于 Qt 小部件的 Material Design 规范实现。 项目地址 项目地址&#xff1a;qt-material-widgets 本地构建环境 Win11 家庭中文版 VS2019 Qt5.15.2 (MSVC2019) 本地构建流程 克隆后的目录结构如图&#xff1a; 直接使用Qt Crea…

游戏引擎学习第147天

仓库:https://gitee.com/mrxiao_com/2d_game_3 上一集回顾 具体来说&#xff0c;我们通过隐式计算来解决问题&#xff0c;而不是像数字微分分析器那样逐步增加数据。我们已经涵盖了这个部分&#xff0c;并计划继续处理音量问题。不过&#xff0c;实际上我们现在不需要继续处理…

uni-app打包成H5使用相对路径

网上找了一圈&#xff0c;没用&#xff0c;各种试&#xff0c;终于给试出来了&#xff0c;主要是网络上的没有第二步&#xff0c;只有第一步&#xff0c;导致打包之后请求的路径没有带上域名 运行的基础路径设置为./ config.js文件里面的baseUrl路径改成空字符&#xff0c;千万…