【Linux】 线程池

线程池

什么是线程池?
一次预先申请一批线程,让这批线程有任务,就处理任务;没任务,就处于等待状态。
为什么要有线程池?
以空间换时间,预先申请一批线程,当有任务到来,可以直接指派给线程执行。

// task.hpp
#pragma once#include <functional>using namespace std;typedef function<int(int, int)> calc_func_t;class Task
{
public:Task() {}Task(int x, int y, calc_func_t func): _x(x), _y(y), _calc_func(func){}// 加法计算的任务int operator()() { return _calc_func(_x, _y); }int get_x() { return _x; }int get_y() { return _y; }
private:int _x;int _y;calc_func_t _calc_func;
};
// log.hpp
#pragma once#include <string>
#include <stdarg.h>
#include <unordered_map>using namespace std;#define LOG_FILE "./threadpool.log"// 日志是有日志级别的
enum LogLevel
{DEBUG,NORMAL,WARNING,ERROR,FATAL
};// 针对枚举类型的哈希函数
template <typename T>
class EnumHash
{
public:size_t operator()(const T& t) const { return static_cast<size_t>(t); }
};
unordered_map<LogLevel, string, EnumHash<LogLevel>> logLevelMap = {{DEBUG, "DEBUG"},{NORMAL, "NORMAL"},{WARNING, "WARNING"},{ERROR , "ERROR"},{FATAL, "FATAL"}
};// 完整的日志功能,至少有:日志等级 时间 支持用户自定义
void logMessage(LogLevel log_level, const char* format, ...)
{
#ifndef DEBUG_SHOWif(log_level == DEBUG) return; // DEBUG_SHOW没有定义,不展示DEBUG信息
#endif char stdBuffer[1024]; // 标准部分char logBuffer[1024]; // 自定义部分time_t timestamp = time(nullptr);struct tm* ploct = localtime(&timestamp);snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%04d-%02d-%02d %02d:%02d:%02d]", logLevelMap[log_level].c_str(),\1900 + ploct->tm_year, 1 + ploct->tm_mon, ploct->tm_mday, ploct->tm_hour, ploct->tm_min, ploct->tm_sec);va_list args;va_start(args, format);vsnprintf(logBuffer, sizeof logBuffer, format, args);va_end(args);FILE* log_file = fopen(LOG_FILE, "a");fprintf(log_file, "%s %s\n", stdBuffer, logBuffer);fclose(log_file);
}

va_*系列函数与vprintf系列函数配合使用可以格式化打印传入的可变参数的内容。
在这里插入图片描述
在这里插入图片描述

// thread.hpp
#pragma once#include <string>
#include <cstdio>
#include <pthread.h>using namespace std;// 对应创建线程时的routine函数的类型
typedef void*(*func_t)(void*);class ThreadData
{
public:void* _ptpool; // 指向线程池对象string _name;
};class Thread
{
public:Thread(int num, func_t callBack, void* _ptpool): _func(callBack){char nameBuffer[64];snprintf(nameBuffer, sizeof(nameBuffer), "Thread_%d", num);_tdata._name = nameBuffer;_tdata._ptpool = _ptpool;}void start() { pthread_create(&_tid, nullptr, _func, (void*)&_tdata); }void join() { pthread_join(_tid, nullptr); }const string& name() { return _tdata._name; }
private:pthread_t _tid; // 线程IDfunc_t _func; // 线程routineThreadData _tdata; // 线程数据
};
// threadPool.hpp
#pragma once#include <vector>
#include <queue>
#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"const int g_thread_num = 3;// 线程池:本质是生产消费模型
template<class T>
class threadPool
{
private:threadPool(int thread_num = g_thread_num): _thread_num(thread_num){pthread_mutex_init(&_lock, nullptr);pthread_cond_init(&_cond, nullptr);for(int i = 0; i < _thread_num; ++i){_threads.push_back(new Thread(i + 1/*线程编号*/, routine, this/*可以传this指针*/));}}threadPool(const threadPool<T>&) = delete;const threadPool<T>& operator=(const threadPool<T>&) = delete;
public:// 考虑多个线程使用单例的情况static threadPool<T>* getThreadPool(int thread_num = g_thread_num){if(nullptr == _pthread_pool){lockGuard lock_guard(&_pool_lock);// 在单例创建好后,锁也就没有意义了// 将来任何一个线程要获取单例,仍必须调用getThreadPool接口// 这样一定会存在大量的申请和释放锁的行为// 所以外层if判断,用于在单例创建的情况下,拦截大量的线程因请求单例而访问锁的行为if(nullptr == _pthread_pool){_pthread_pool = new threadPool<T>(thread_num);}}return _pthread_pool;}void run(){for(auto& pthread : _threads){pthread->start();logMessage(NORMAL, "%s %s", (pthread->name()).c_str(), "启动成功");}}void pushTask(const T& task){lockGuard lock_guard(&_lock);_task_queue.push(task);pthread_cond_signal(&_cond);}~threadPool(){for(auto& pthread : _threads){pthread->join();delete pthread;}pthread_mutex_destroy(&_lock);pthread_cond_destroy(&_cond);}
public:pthread_mutex_t* getMutex(){return &_lock;}bool isEmpty(){return _task_queue.empty();}void waitCond(){pthread_cond_wait(&_cond, &_lock);}T& getTask(){T& task = _task_queue.front();_task_queue.pop();return task;}
private:// 消费过程static void* routine(void* args){ThreadData* tdata = (ThreadData*)args;threadPool<T>* tpool = (threadPool<T>*)tdata->_ptpool;while(true){T task;{lockGuard lock_guard(tpool->getMutex());while (tpool->isEmpty()) tpool->waitCond();task = tpool->getTask();}logMessage(WARNING, "%s 处理完成: %d + %d = %d", (tdata->_name).c_str(), task.get_x(), task.get_y(), task());}}
private:vector<Thread*> _threads; // 数组存放创建的线程的地址int _thread_num; // 创建的线程个数queue<T> _task_queue; // 阻塞式任务队列pthread_mutex_t _lock; // 针对任务队列的锁pthread_cond_t _cond; // 队列空满情况的条件变量static threadPool<T>* _pthread_pool; // 饿汉式线程池static pthread_mutex_t _pool_lock; // 针对线程池的锁
};template<class T>
threadPool<T>* threadPool<T>::_pthread_pool = nullptr;
template<class T>
pthread_mutex_t threadPool<T>::_pool_lock = PTHREAD_MUTEX_INITIALIZER;
// test.cc
#include "task.hpp"
#include "threadPool.hpp"
#include <unistd.h>
#include <ctime>void test1()
{srand((unsigned int)time(nullptr) ^ getpid());threadPool<Task>::getThreadPool()->run();while(true){// 生产的过程 - 制作任务的时候要花时间的int x = rand() % 100 + 1;usleep(2023);int y = rand() % 50 + 1;Task task(x, y, [](int x, int y){ return x + y; });logMessage(DEBUG, "制作任务完成: %d + %d = ?", x, y);// 推送任务到线程池threadPool<Task>::getThreadPool()->pushTask(task);sleep(1);}
}
# Makefile
test:test.ccg++ -o $@ $^ -std=c++11 -lpthread -DDEBUG_SHOW
.PHONY:clean
clean:rm -f test

运行结果:
在这里插入图片描述

自旋锁

自旋锁:本质是通过不断检测锁的状态,来确定资源是否就绪的方案。
什么时候使用自旋锁?这个由临界资源就绪的时间长短决定。
自旋锁的初始化 & 销毁:
在这里插入图片描述
自旋锁的加锁:
在这里插入图片描述
自旋锁的解锁:
在这里插入图片描述

读者写者问题

写者与写者:互斥关系
读者与写者:互斥 & 同步关系
读者与读者:共享关系

读者写者问题和生产消费模型的本质区别在于,消费者会拿走数据(做修改),而读者不会。
读写锁的初始化 & 销毁:
在这里插入图片描述
读写锁之读者加锁:
在这里插入图片描述
读写锁之写者加锁:
在这里插入图片描述
读写锁的解锁:
在这里插入图片描述
关于是读者还是写者优先的问题,抛开应用场景去谈技术细节就是耍流氓。
而pthread库中的读写锁默认采用读者优先,这类的应用场景主要是:数据被读取的频率非常高,被修改的频率非常低。

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

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

相关文章

将rtsp视频流发送到AWS Kinesis Video Streams的方案——使用Gstreamer(C++) Command Line

大纲 1 创建Kinesis Video Streams1.1 创建视频流1.2 记录Creation Time 2 创建策略2.1 赋予权限2.2 限制资源2.3 Json格式描述&#xff08;或上面手工设置&#xff09;2.4 注意事项 3 创建IAM用户3.1 生成密钥对3.2 附加策略3.3 记录访问密钥对 4 编译C 创建者库5 发送6 检查参…

JavaScript <关于逆向RSA非对称加密算法的案例(代码剖析篇)>--案例(五点一)

引用上文: CSDNhttps://mp.csdn.net/mp_blog/creation/editor/134857857 剖析: var bitsPerDigit16; // 每个数组元素可以表示的二进制位数// 数组复制函数&#xff0c;将源数组部分复制到目标数组的指定位置 function arrayCopy(src, srcStart, dest, destStart, n) {var m…

git提交代码报错Git: husky > pre-commit

目录 git提交代码报错原因解决方法&#xff08;三种&#xff09;1、第一种2、第二种3、第三种 git提交代码报错原因 这个问题是因为当你在终端输入git commit -m “XXX”,提交代码的时候,pre-commit(客户端)钩子&#xff0c;它会在Git键入提交信息前运行做代码风格检查。如果代…

【小白专用】MySQL创建数据库和创建数据表

1.在Windows开始搜索输入Mysql,并选择第一个打开。 2.输入安装时的密码 3.说明安装成功。 二、创建数据库 1. 连接 MySQL 输入 mysql -u root -p 命令&#xff0c;回车&#xff0c;然后输入 MySQL 的密码(不要忘记了密码)&#xff0c;再回车&#xff0c;就连接上 MySQL 了。 …

深入Redis过程-持久化

目录 redis实现持久化 RDB 触发机制-定期方法 定期-手动触发 save bgsave 定期-自动触发 AOF 开启AOF功能 刷新缓冲区策略 重写机制 混合持久化 Redis事务 事务相关的命令 MULTI EXEC DISCARD WATCH redis实现持久化 RDB RDB叫做Redis数据备份文件&#xf…

强大的公式编辑器 —— MathType最新版本安装与使用

强大的公式编辑器 —— MathType最新版本安装与使用 由于使用了很长时间的机械硬盘出现坏道&#xff0c;安装在其中的MathType6.9&#xff08;精简版&#xff09;也没办法使用了&#xff0c;本来想安装个高版本的MathType&#xff0c;比如MathType7.4&#xff0c;但在网上苦苦…

如何更改Jupyter Notebook中的环境?

1.首先&#xff0c;打开终端 2.接着&#xff0c;分别输入以下命令 conda env list 把EXPose替换为自己的环境变量 conda activate EXPose 3.接下来安装‘ ipykernel ’软件包 conda install ipykernel 4. 将该环境添加到Jupyter Notebook中&#xff1b;在Jupyter Notebook…

小白第一次开私服怎么吸引玩家

大家好&#xff0c;我是咕噜-凯撒&#xff0c;在现在这个网络社会很多人为了放松一下会选择打打游戏&#xff0c;私服也就成为了许多玩家为了寻找新鲜体验的热门选择&#xff0c;很多小白就发现了这个契机但是吸引玩家加入自己的服务器也就成了一个比较头疼的问题&#xff0c;下…

Wrong number of values of control parameter 2(Halcon 错误代码:1402)

threshold (ImageReduced1, Region, 0,min2(75,Min)) 程序运行到这一句&#xff0c;出现错误 原因是其中的参数Min为空数组 解决方案&#xff1a;判断了下可以输出Min的区域是否存在&#xff0c;不存在跳过这一步。

mybatis多表映射-分步查询

1、建库建表 create database mybatis-example; use mybatis-example; create table t_book (bid varchar(20) primary key,bname varchar(20),stuid varchar(20) ); insert into t_book values(b001,Java,s001); insert into t_book values(b002,Python,s002); insert into …

函数的栈帧

我们每次在调用函数的时候&#xff0c;都说会进行传参。每次创建函数&#xff0c;或者进行递归的时候&#xff0c;也会说会进行压栈。 那么&#xff0c;今天我们就来具体看看函数到底是如何进行压栈&#xff0c;传参的操作。 什么是栈&#xff1f; 首先我们要知道&#xff0c;…

Error opening file for writing报错解决

报错展示及描述 在安装pycharm的时候出现了一下报错&#xff0c; Error opening file for writing。 报错原因 一般出现这种报错都是文件权限的原因&#xff0c;检查一下&#xff0c;果然这个文件夹权限是【只读】 查看文件权限的方式&#xff1a;【右击】文件夹名称&#xff0…

046:vue通过axios调用json地址数据的方法

第046个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

ffmpeg过滤器filter理论与实战

文章目录 前言一、DirectShow1、简介2、程序基本结构3、架构 二、过滤器1、视频过滤器 -vf2、音频过滤器 -af3、过滤器链&#xff08;Filterchain&#xff09;4、过滤器图&#xff08;Filtergraph&#xff09;①、基本语法②、Filtergraph 的分类 5、结构体间的关系图 三、过滤…

保研毕业论文查重率多少通过【保姆教程】

大家好&#xff0c;今天来聊聊保研毕业论文查重率多少通过&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 保研毕业论文查重率多少通过 在保研过程中&#xff0c;毕业论文的查重率是衡量学术诚信和论文…

JAVA8新特性之函数式编程详解

JAVA8新特性之函数式编程详解 前言一、初步了解函数式接口二、 Lambda表达式2.1 概述2.2 lambda省略规则2.3 lambda省略常见实例2.4 lambda表达式与函数式接口 三、 Stream流3.1 stream流的定义3.2 Stream流的特点3.3 Stream流的三个步骤3.4 Stream 和 Collection 集合的区别&a…

【HarmonyOS开发】拖拽动画的实现

动画的原理是在一个时间段内&#xff0c;多次改变UI外观&#xff0c;由于人眼会产生视觉暂留&#xff0c;所以最终看到的就是一个“连续”的动画。UI的一次改变称为一个动画帧&#xff0c;对应一次屏幕刷新&#xff0c;而决定动画流畅度的一个重要指标就是帧率FPS&#xff08;F…

【带头学C++】----- 九、类和对象 ---- 9.12 C++之友元函数(9.12.1---12.4)

❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️创做不易&#xff0c;麻烦点个关注❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️ ❤️❤️❤️❤️❤️❤️❤️❤️❤️文末有惊喜&#xff01;献舞一支&#xff01;❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️ 目录 9.12…

五:爬虫-数据解析之xpath解析

三&#xff1a;数据解析之xpath解析 1.xpath介绍&#xff1a; ​ xpath是XML路径语言&#xff0c;它可以用来确定xml文档中的元素位置&#xff0c;通过元素路径来完成对元素的查找&#xff0c;HTML就是XML的一种实现方式&#xff0c;所以xpath是一种非常强大的定位方式​ XPa…

vue2 element-ui select下拉框 选择传递多个参数

<el-select v-model"select" slot"prepend" placeholder"请选择" change"searchPostFn($event,123)"> <el-option :label"item.ziDianShuJu" :value"{value:item.id, label:item.ziDianShuJu}" v-for&qu…