《从内核视角看 Linux:环形缓冲区 + 线程池的生产消费模型实现》 - 指南

news/2026/1/26 9:14:19/文章来源:https://www.cnblogs.com/ljbguanli/p/19531451

《从内核视角看 Linux:环形缓冲区 + 线程池的生产消费模型实现》 - 指南

前引:环形缓冲区的低开销数据传输、线程池的灵活资源调度,让二者的结合成为 Linux 高性能并发的 “黄金搭档”。无论是服务器开发、嵌入式编程还是底层工具构建,掌握环形生产消费模型与线程池的设计逻辑,都能帮你突破并发性能瓶颈。接下来,我们从基础到进阶,逐步解锁这两项核心技术!

目录

【一】环形生产消费模型介绍

【二】信号量使用

(1)创建信号量对象

(2)初始化信号量

(3)信号量等待

(4)信号量释放

(5)信号量销毁

(6)获取当前信号量

【三】模型实现

(1)理论框架

(2)生产实现

(3)消费实现

【四】线程池

(1)理论框架

(2)生产实现

(3)消费实现

(4)任务执行

(5)main函数

(6)效果展示

【五】单例模式

(1)什么是单例模式

(2)如何完成单例模式

(3)例如:


【一】环形生产消费模型介绍

“环形”生产消费模型:队列采⽤数组模拟,⽤模运算来模拟环状特性,例如:

特点:

(1)缓存大小初始后是固定的

(2)也符合数据“先进先出”的特点

(3)通过首尾下标来访问数据。Head:只要有空位置就可以一直存放数据

 Tail:只要有数据就可以一直获取数据

而位置是否有数据我们可以根据信号量(引用计数,本质为临界资源数量)判断!

(此模型较于普通的“生产消费模型”可以根据信号量做到并发执行,信号量代表资源数量)

为什么信号量不用担心两个线程同时获取一个信号量?

核心原因是sem_wait操作的原子性—— 它将 “检查信号量值” 和 “修改信号量值” 封装为一个不可分割的步骤,确保任何时刻只有一个线程能成功获取资源

例如:核心思想就是“连续”,不给其它线程可乘之机

原子操作的关键是 “没有中间状态”:

  • 信号量值要么是 1(没被拿),要么是 0(被拿了),不会出现 “0.5” 这种中间值;
  • 线程要么拿到资源(信号量值变 0),要么没拿到(阻塞),不会出现 “两个线程都认为自己拿到了” 的情况。

这就是为什么哪怕两个线程 “同时” 抢最后一个信号量,也绝对不会同时拿到 —— 原子性把 “争夺” 变成了 “排队”,先到先得

【二】信号量使用

(1)创建信号量对象

原型:

sem_t 对象变量名;
(2)初始化信号量

原型:

#include 
int sem_init(sem_t *sem, int pshared, unsigned int value);

参数:

sem:指向信号量对象的指针(需提前声明,如sem_t empty_sem;

pshared:0 表示信号量用于线程间同步(环形模型必选);非 0 表示用于进程间同步(不常用)

value:信号量初始值(表示可用资源的初始数量)

作用:初始化一个信号量

(3)信号量等待

原型:

int sem_wait(sem_t *sem);

参数:指向目标信号量对象的指针

作用:尝试获取信号量资源(将信号量值减 1)

           若当前信号量值为 0,线程会阻塞等待,直到有其他线程释放资源(信号量值 > 0)

(4)信号量释放

原型:

int sem_post(sem_t *sem);

参数:指向目标信号量对象的指针

作用:释放信号量资源(将信号量值加 1),并唤醒一个阻塞在该信号量上的线程(若有)

(5)信号量销毁

原型:

int sem_destroy(sem_t *sem);

参数:指向目标信号量对象的指针

作用:销毁已初始化的无名信号量,释放其占用的资源

(6)获取当前信号量

原型:

int sem_getvalue(sem_t *sem, int *sval);

参数:获取信号量的当前值

第一个参数:要获取的信号量对象指针

第二个参数:用于存储信号量当前值

作用:

【三】模型实现

(1)理论框架

生产和消费是根据信号量来判断的,可以同步,所以需要两个信号量初始化

(生产:初始资源为MAX)

(消费:初始资源为0)

在单生产消费的基础上,同样只能单个线程生产与消费,但是可以并发执行,所以需要两个互斥锁

所以类的设计:

//最大容量
#define MAX 5
template
class cycle_p_c
{
public:cycle_p_c(){//开缓存空间_buffer.resize(MAX);//锁pthread_mutex_init(&p_mutex,NULL);pthread_mutex_init(&c_mutex,NULL);//信号量sem_init(&p_message,0,MAX);sem_init(&c_message,0,0);//下标head,tail=0;}//生产void push_back(const T& date){}//消费void pop(){}
private://数据缓存std::vector _buffer;//信号变量*2sem_t p_message;sem_t c_message;//两个下标size_t head;size_t tail;//两个互斥锁(确保每次只有一个生产和一个消费并发执行)pthread_mutex_t p_mutex;pthread_mutex_t c_mutex;
};
(2)生产实现

先判断信号量,如果生产的信号量充足(原子性),就可以直接去进入生产(否则就自动等待):

生产需要保证互斥性,否则可能导致超出资源范围,根据模运算来获取资源下标

//生产void push_back(const T& date){//看信号量是否足够(生产信号量-1)sem_wait(&p_message);//此时说明已经申请到信号量pthread_mutex_lock(&p_mutex);//生产head%=MAX;_buffer[head++]=date;//消费信号量+1sem_post(&c_message);std::cout<<"我生产了:"<
(3)消费实现

先判断信号量,如果消费的信号量不为0(原子性),就可以直接去进入消费(否则就自动等待)

消费需要保证互斥性,否则可能导致超出资源范围,根据模运算来获取资源下标

//消费void pop(){//看信号量是否足够(消费信号量-1)sem_wait(&c_message);//此时说明已经申请到信号量pthread_mutex_lock(&c_mutex);//消费std::cout<<"我消费了:"<<_buffer[tail]<<"...."<

【四】线程池

线程池我们采用上面的“生产消费模型”或者“环形生产消费模型”来实现都可以,这里以后者为例:

我的设计思路:

主要还是环形生产消费模型的实现,其次在pop()的时候只需要将任务拿出来交给执行函数就可以了,因为是一个线程一次pop(),所以执行任务是不需要加锁的,当然如果你需要打印的话就需要加锁,且所有打印应该是同一把锁,否则打印无法准确观察任务的执行

(1)理论框架
//最大容量
#define MAX 6
//随机数范围
constexpr int min_t=1;
constexpr int max_t=100;
struct Date
{Date() = default;Date(int date1,int date2):_date1(date1),_date2(date2){}int _date1;int _date2;
};
//获取随机数
int data_t()
{// 1. 使用 std::thread_local 声明一个线程局部的 Mersenne Twister 生成器//    std::mt19937 是一个高质量的伪随机数生成器thread_local static std::mt19937 generator;// 2. 懒初始化:检查生成器是否已经被种子化//    (一个未被种子化的 std::mt19937 会产生固定的默认序列)thread_local static bool is_initialized = false;if (!is_initialized) {// 3. 使用 std::random_device 来获取一个真随机种子std::random_device rd;// 为了确保万无一失,我们可以将多个种子源组合起来// 这里我们结合了真随机数和线程ID的哈希值std::seed_seq seq{rd(),static_cast(std::hash()(std::this_thread::get_id()))};// 4. 用组合后的种子序列来初始化当前线程的生成器generator.seed(seq);is_initialized = true;}// 5. 定义一个分布,用于生成 [min_t, max_t] 范围内的整数std::uniform_int_distribution distribution(min_t, max_t);// 6. 使用生成器和分布来产生最终的随机数return distribution(generator);
}
//任务执行方法
int calculate(struct Date date)
{return date._date1+date._date2;
}
//线程池
template
class cycle_p_c
{
public:cycle_p_c(){//缓存空间_buffer.resize(MAX);//锁pthread_mutex_init(&p_mutex,NULL);pthread_mutex_init(&c_mutex,NULL);pthread_mutex_init(&tasks,NULL);//信号量sem_init(&p_message,0,MAX);sem_init(&c_message,0,0);//下标tail=0;head=0;}~cycle_p_c(){pthread_mutex_destroy(&p_mutex);pthread_mutex_destroy(&c_mutex);pthread_mutex_destroy(&tasks);sem_destroy(&p_message);sem_destroy(&c_message);}//生产void push_back(const T& date){}//消费T pop(){}//处理任务void Handle_tasks(struct Date* ptr){}
private://数据缓存std::vector _buffer;//信号变量*3sem_t p_message;sem_t c_message;//两个下标size_t head;size_t tail;//两个互斥锁(确保每次只有一个生产和一个消费并发执行)pthread_mutex_t p_mutex;pthread_mutex_t c_mutex;//任务拿取锁pthread_mutex_t tasks;
};
(2)生产实现

先看生产信号量(原子性,不需要加锁)是否充足,然后开启互斥锁,开始生产,给消费信号

 //生产void push_back(const T& date){//看信号量是否足够(生产信号量-1)sem_wait(&p_message);//此时说明已经申请到信号量pthread_mutex_lock(&p_mutex);//生产head%=MAX;_buffer[head++]=date;//消费信号量+1sem_post(&c_message);std::cout<<"我创建任务了哦..."<
(3)消费实现

看消费的信号是否充(原子性),随后开启锁,开始消费,返回任务,用于后面的任务执行函数

  //消费T pop(){//看信号量是否足够(消费信号量-1)sem_wait(&c_message);//此时说明已经申请到信号量pthread_mutex_lock(&c_mutex);//消费struct Date date=_buffer[tail++];tail%=MAX;//生产信号量+1sem_post(&p_message);pthread_mutex_unlock(&c_mutex);return date;}
(4)任务执行

如果需要打印任务来进行就需要加锁,确保每个任务的执行打印是独一无二的,否则会乱序

//处理任务void Handle_tasks(struct Date* ptr){pthread_mutex_lock(&tasks);std::cout<<"date1="<<(ptr->_date1)<<" date2="<<(ptr->_date2)<<" "<<"date1+date2=="<
(5)main函数
#include"thread_pool.h"
typedef cycle_p_c cycle;
//派发任务
void* haddle_p(void* arg)
{cycle *ptr=(cycle*)arg;//创建任务int date1=data_t();int date2=data_t();struct Date date(date1,date2);sleep(1);ptr->push_back(date);return NULL;
}
//处理任务
void* haddle_c(void* arg)
{cycle *ptr=(cycle*)arg;//先获取任务struct Date date=ptr->pop();//执行任务ptr->Handle_tasks(&date);return NULL;
}
int main()
{cycle *ptr=new cycle;while(1){//任务生成pthread_t pthread_p;pthread_create(&pthread_p,NULL,haddle_p,ptr);//任务执行线程pthread_t pthread_c;pthread_create(&pthread_c,NULL,haddle_c,ptr);sleep(1);}return 0;
}
(6)效果展示

如图出现“一次创建”“两次打印任务”是因为从内核来看该线程的时间片到了:线程完成对信号量的操作,此时有了消费信号,但无法执行打印,而生产和消费是并发执行的,此时就轮到消费了,生产需要重新进入运行队列重新执行,因此将打印提前,在线程的时间片结束前执行打印即可解决!

【五】单例模式

(1)什么是单例模式

某些类,只应该具有⼀个对象(实例),就称之为单例。这点我们将构造私,通过静态方式即可解决!

(为什么需要静态的?静态成员在进程形成时就已经形成,而普通类需要main函数栈帧形成)

而静态方式可以选择直接实例化对象,也可以先只形成对象指针(用的时候再实例化)

很明显,选择后者更佳,二者虽都是实例化一个对象,但是后者可以一定时刻减少内存负担!

(2)如何完成单例模式

总结:构造私有(不能delete,否则会调用系统默认的)+delete拷贝构造与赋值+锁+静态成             员内部形成唯一的对象指针并返回

(3)例如:
#include
#include
#include
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
template
class Ceshi
{
public:// 禁止拷贝构造和赋值操作Ceshi(const Ceshi&) = delete;Ceshi& operator=(const Ceshi&) = delete;static Ceshi* handle(){if (instance == nullptr){pthread_mutex_lock(&mutex);if (instance == nullptr){instance = new Ceshi;}pthread_mutex_unlock(&mutex);}return instance;}void text(){std::cout<<"Hello World"<* instance;
};
// 模板类静态成员变量类外初始化
template
Ceshi* Ceshi::instance = nullptr;

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

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

相关文章

聊聊唐山婚姻家事法律服务品牌,靠谱的是哪家,价格如何?

近有不少天津、唐山的朋友问我,想找一家靠谱的婚姻家事法律服务公司,处理离婚、财产分割这些事,但又不知道怎么选。其实选对律所关键看三点:专业度、服务模式和口碑。天津合华律师事务所就是个不错的例子,他们专注…

基于nRF52832的SD卡文件系统操作实现指南

一、硬件连接与配置引脚映射 nRF52832的SPI接口与SD卡引脚对应关系(以SPI0为例):SD卡引脚 nRF52832引脚 功能说明CS P0.17 片选信号(主动低电平)SCK P0.19 时钟信号MOSI P0.20 主设备输出/从设备输入MISO P0.21 主…

2026年首月project管理工具核心性能实测:系统稳定性与团队协作效率的综合绩效推荐

随着企业数字化转型进入深水区,project管理工具已成为组织提升交付效率、实现战略目标的关键基础设施。2026年首月,我们围绕系统稳定性、跨团队适配能力、协作提效成果、安全合规保障四大核心维度,对国内多家主流pr…

【含文档+PPT+源码】基于Python的博客系统的设计与实现

项目介绍本课程演示的是一款基于Python的博客系统的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料带你从零开始部署运行本套系统该项目附带的源码资…

AI听出开心和愤怒?SenseVoiceSmall情感识别亲测

AI听出开心和愤怒&#xff1f;SenseVoiceSmall情感识别亲测 你有没有想过&#xff0c;一段语音不只是“说了什么”&#xff0c;更藏着“怎么说话”——是轻快带笑&#xff0c;还是压抑低沉&#xff1f;是突然爆发的愤怒&#xff0c;还是强忍哽咽的悲伤&#xff1f;传统语音识别…

Multisim模拟电路仿真实战案例:基于运算放大器的设计

以下是对您提供的博文内容进行深度润色与结构重构后的专业级技术文章。整体风格更贴近一位资深模拟电路工程师在技术博客或内训分享中的真实表达——去AI腔、强逻辑链、重实战感、有教学温度&#xff0c;同时严格遵循您提出的全部优化要求&#xff08;无模板化标题、无总结段、…

SGLang缓存预取功能实测,长文本处理快如闪电

SGLang缓存预取功能实测&#xff0c;长文本处理快如闪电 在大模型推理服务走向高并发、长上下文、多轮交互的今天&#xff0c;“重复计算”正成为拖慢响应速度、抬高GPU成本的隐形杀手。尤其当用户连续提交相似前缀的请求——比如客服对话中反复出现“您好&#xff0c;我想查询…

零基础入门:理解理想二极管选型的基本参数

以下是对您提供的技术博文进行 深度润色与专业重构后的版本 。本次优化严格遵循您的全部要求&#xff1a; ✅ 彻底去除AI痕迹&#xff0c;语言自然、有“人味”、具教学感与实战温度&#xff1b; ✅ 打破模块化标题结构&#xff0c;以逻辑流替代章节切割&#xff0c;全文一…

小白也能用的AI修图工具:科哥镜像保姆级使用教程

小白也能用的AI修图工具&#xff1a;科哥镜像保姆级使用教程 你是不是也遇到过这些情况—— 一张精心拍摄的照片&#xff0c;却被路人闯入画面&#xff1b; 电商主图上碍眼的水印怎么都去不干净&#xff1b; 老照片边缘有划痕&#xff0c;想修复又怕越修越糟&#xff1b; 甚至…

测试开机启动脚本镜像测评:自动化配置原来这么简单

测试开机启动脚本镜像测评&#xff1a;自动化配置原来这么简单 你是否也经历过这样的场景&#xff1a;刚部署好一台设备&#xff0c;需要反复手动运行初始化脚本&#xff1b;每次重启后又要重新启动服务&#xff1b;团队新成员配置环境耗时半天&#xff0c;还总出错&#xff1…

1999-2024年 上市公司-高学历人才数据(+文献)

01、数据简介 本研究聚焦中国上市公司人力资本情况。对于高学历人才的界定&#xff0c;参考了《管理学报》2024年刘硕、李香菊在《财政压力对企业数字化转型的影响研究》中对上市公司高学历人才的定义&#xff0c;以具有硕士研究生及以上学历的人数来衡量高学历人才数量&#…

2000-2024年 上市公司-会计稳健性指标-ACF模型、CScore模型、Basu模型(+文献)

01、数据简介 会计稳健性&#xff0c;亦称谨慎性原则&#xff0c;依国际财务报告准则&#xff0c;要求企业于会计确认、计量及报告交易或事项时&#xff0c;秉持审慎态度&#xff0c;避免高估资产或收益、低估负债或费用。2000-2024年上市公司-会计稳健性指标-ACF模型、CScore模…

初学者必备的ESP32 Arduino环境搭建注意事项

以下是对您提供的博文进行 深度润色与重构后的技术文章 。整体遵循您的全部要求&#xff1a; ✅ 彻底去除AI痕迹&#xff0c;语言自然如资深工程师现场讲解&#xff1b; ✅ 摒弃所有模板化标题&#xff08;引言/总结/展望&#xff09;&#xff0c;代之以逻辑递进、层层深入…

笔记本电脑,闪屏白屏黑屏,笔记本电脑看不清楚,闪来闪去歇性闪屏,电脑放视频看不清楚老闪

黑屏闪屏一般指电脑显示器上的显示问题&#xff0c;电脑在运行过程中&#xff0c;屏幕画面出现闪烁或不规则闪动&#xff0c;有时会出现横条线和竖条线。闪屏和雪花屏主要是显卡的问题造成的&#xff0c;雪花屏类似电视的雪花屏&#xff0c;闪屏就像显卡驱动有问题那样&#xf…

Open-AutoGLM助力老年人操作手机,无障碍应用探索

Open-AutoGLM助力老年人操作手机&#xff0c;无障碍应用探索 在智能手机功能日益丰富的今天&#xff0c;一个现实困境正悄然浮现&#xff1a;超过2.8亿中国老年人面临“数字鸿沟”——不是买不起新手机&#xff0c;而是看不懂图标、点不准按钮、记不住步骤。一次微信支付失败、…

深圳青春期教育咨询室评测:助力家庭教育新方向,家庭教育指导/青少年心理咨询/青少年厌学/青春期教育,家庭教育训练营怎么选

评测背景 随着社会竞争压力加剧与家庭结构变化,青春期青少年心理健康问题日益凸显。据教育部及权威机构统计,全国超30%的青少年存在不同程度的心理困扰,其中深圳作为改革开放前沿城市,家庭教育需求呈现多元化、精细…

支持热更新的配置文件解析方案详解

以下是对您提供的博文《支持热更新的配置文件解析方案详解》进行 深度润色与结构重构后的技术文章 。本次优化严格遵循您的全部要求&#xff1a; ✅ 彻底去除AI痕迹&#xff0c;语言自然、专业、有“人味”——像一位在一线踩过坑、写过百万行配置管理代码的资深工程师在分享…

【Matlab】MATLAB ones 函数:从全 1 矩阵生成到固定值批量赋值,高效构建标准化数据载体

精通 MATLAB ones 函数:从全 1 矩阵生成到固定值批量赋值,高效构建标准化数据载体 在 MATLAB 数据处理体系中,ones函数是与zeros并列的核心初始化工具,其核心功能是生成指定维度的全 1 矩阵(或多维数组),并可通过简单运算实现任意固定值的批量赋值。相比手动逐元素赋值…

2026年project管理工具权威测评报告:基于百家客户匿名反馈的口碑深度解析

数字化进程加速与企业协作模式革新,推动项目管理从传统流程管控向智能协同与价值交付转型,一体化project管理工具已成企业数字化转型的核心支柱。本报告基于2026年Q1百余家跨行业客户的匿名深度访谈及反馈,从产品功…