并发编程的守护者:信号量与日志策略模式解析

一、信号量

关于信号量的介绍在深入Linux内核:IPC资源管理揭秘
这篇文章当中已经做了初步的介绍了,相信大家对于信号量已经有了初步的认知了。

今天,我们就来探讨如何实现信号量。

1. 信号量的接口

//初始化信号量
//成功了,返回0,失败了,返回-1并且设置错误码
//sem初始化的信号量
//pshared设置为0,代表线程间使用
//value信号量的初始值
int sem_init(sem_t* sem, int pshared, unsigned int value);
//销毁信号量
//成功返回0,失败返回-1并且设置错误码
int sem_destroy(sem_t* sem);
//减少信号量
//成功返回0,失败返回-1并且设置错误码
int sem_wait(sem_t* sem);
//增加信号量
//成功返回0,失败返回-1并且设置错误码
int sem_post(sem_t* sem);

2. 信号量实现的一些细节问题

信号量的接口就了解到这里。我们实现的信号量是基于一个环形队列实现的(数组)

接下来,我们了解实现的一些细节。

在这里插入图片描述

队列的容量是有限的,刚开始时,队列为空,一定是生产者先运行。此时生产者和消费者访问同一个位置,生产者还没生产数据,消费者就开始消费数据,这是不行的,所以,必须等到生产者生产数据之后,消费者才可以消费数据。所以,生产者和消费者之间需要维护互斥与同步的关系

在这里插入图片描述

当队列为满时,必须让消费者先运行。此时生产者,消费者又指向了同一个位置,当消费者拿取数据时,生产者是不能立即生产数据的,要不然消费者还没有获取到数据,生产者已经把数据覆盖了,不就导致数据错乱了吗!所以,这个过程不仅需要生产者和消费者互斥的获取数据,还需要同步

当队列不为空,不为满时,生产者和消费者肯定不是指向同一个位置的,所以,生产者和消费者不就可以并发执行了

3. 信号量的实现

Sem.hpp

#pragma once
#include<iostream>
#include<vector>
#include<unistd.h>
#include<semaphore.h>class Sem
{
public:Sem(int num):_initnum(num){sem_init(&_sem, 0, _initnum);}void P(){int n = sem_wait(&_sem);}void V(){int n = sem_post(&_sem);}~Sem(){sem_destroy(&_sem);}
private:sem_t _sem;int _initnum;
};

RingQueue.hpp

#include"Sem.hpp"int gcap = 5;
template<typename T>
class RingQueue
{
public:RingQueue(int cap = gcap):_ring_queue(cap),_cap(cap),_space_sem(cap),_data_sem(0),_c_step(0),_p_step(0){}void EnQueue(const T& in){//先申请空间信号量,对资源的一种预定机制_space_sem.P();//生产数据_ring_queue[_p_step++] = in;_p_step %= _cap;_data_sem.V();}void Pop(T* out){//先申请数据信号量_data_sem.P();//消费数据*out = _ring_queue[_c_step++];_c_step %= _cap;_space_sem.V();}~RingQueue(){}
private:std::vector<T> _ring_queue;int _cap;Sem _space_sem;Sem _data_sem;int _c_step;int _p_step;
};

main.cc

#include"RingQueue.hpp"void* consumer(void* args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);while(true){int data = 0;rq->Pop(&data);std::cout << "消费者消费了一个数据" << data << std::endl;}
}void* productor(void* args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);int data = 1;while(true){sleep(1);rq->EnQueue(data);std::cout << "生产者生产了一个数据" << data << std::endl;data++;}
}
int main()
{RingQueue<int>* rq = new RingQueue<int>();pthread_t c, p;pthread_create(&c, nullptr, consumer, (void*)rq);pthread_create(&p, nullptr, productor, (void*)rq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}

Makefile

ringqueue:main.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f ringqueue

4. 信号量实现过程中的一些疑问

这是一个基于单生产者,单消费者的信号量

问题1:我们在申请信号量的过程当中并没有用到锁,难道就不怕数据不安全吗

刚开始时,队列为空,生产者先申请信号量,生产数据,然后在V操作,唤醒消费者,消费者才能消费数据。这个过程本身就已经完成了生产者和消费者之间的互斥与同步关系

当队列为满时,生产者申请信号量失败,就被阻塞住,此时消费者申请信号量,消费数据,然后再唤醒生产者,生产者才能生产数据,所以这个过程本身也完成了生产者与消费者之间的互斥与同步关系

而队列不为空也不为满时,生产者和消费者可以并发执行

问题2:我们怎么没有在临界区内部,判断资源是否就绪呢

信号量本身就是一把计数器,是对于资源的一种预定机制,对信号量进行P操作的时候,虽然是申请信号量,但本质就是对资源是否就绪进行判断。有多少资源就可以预定多少资源,绝不会预定出的资源比实际资源多,也就是说有多少资源就可以有多少个生产者线程

重新理解信号量。

我们把信号量设置为5,如果信号量设置为1呢?不就是二元信号量一个线程申请信号量之后就不可能再有第二个线程成功申请信号量,信号量就变为了0,这不就是一把吗!控制着线程的开关

重新理解一下锁:不就是认为自己的资源只有一份,申请锁不就类似于二元信号量,信号量P操作,释放锁不就是V操作

所以,锁是信号量的一种特殊情况

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、日志与策略模式

什么是日志呢?

计算机中的日志是记录系统和软件运行中发生事件的文件,主要作用是监控运行状态,记录异常信息,帮助快速定位问题并支持程序员进行问题修复,它是系统维护,故障排查和安全管理的重要工具

我们设计的日志格式主要包含以下几个指标:

时间戳、日志等级、日志内容、文件名、行号,进程线程相关 id 信息

//获取时间戳
//tloc设置为nullptr
time_t time(time_t* tloc);
//timep获取到的时间戳
//result输出型参数
struct tm* localtime_r(const time_t* timep, struct tm* result);
struct tm 
{int tm_sec;    /* Seconds (0-60) */int tm_min;    /* Minutes (0-59) */int tm_hour;   /* Hours (0-23) */int tm_mday;   /* Day of the month (1-31) */int tm_mon;    /* Month (0-11) */int tm_year;   /* Year - 1900 */int tm_wday;   /* Day of the week (0-6, Sunday = 0) */int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */int tm_isdst;  /* Daylight saving time */
};

Logger.hpp

#pragma once
#include <iostream>
#include <filesystem>
#include <fstream>
#include <string>
#include <sstream>
#include <memory>
#include <unistd.h>
#include "Mutex.hpp"enum class LoggerLevel
{DEBUG,INFO,WARNING,ERROR,FATAL
};std::string LoggerLevelToString(LoggerLevel level)
{switch (level){case LoggerLevel::DEBUG:return "Debug";case LoggerLevel::INFO:return "Info";case LoggerLevel::WARNING:return "Warning";case LoggerLevel::ERROR:return "Error";case LoggerLevel::FATAL:return "Fatal";default:return "Unknown";}
}std::string GetCurrentTime()
{// 获取时间戳time_t timep = time(nullptr);// 把时间戳转化为时间格式struct tm currtm;localtime_r(&timep, &currtm);// 转化为字符串char buffer[64];snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d-%02d-%02d",currtm.tm_year + 1900, currtm.tm_mon + 1, currtm.tm_mday,currtm.tm_hour, currtm.tm_min, currtm.tm_sec);return buffer;
}class LogStrategy
{
public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string &logmessage) = 0;
};// 显示器刷新
class ConsoleLogStrategy : public LogStrategy
{
public:~ConsoleLogStrategy(){}virtual void SyncLog(const std::string &logmessage) override{{LockGuard lockguard(&_lock);std::cout << logmessage << std::endl;}}private:Mutex _lock;
};const std::string default_dir_path_name = "log";
const std::string default_filename = "test.log";
// 文件刷新
class FileLogStrategy : public LogStrategy
{
public:FileLogStrategy(const std::string dir_path_name = default_dir_path_name,const std::string filename = default_filename): _dir_path_name(dir_path_name), _filename(filename){if (std::filesystem::exists(_dir_path_name)){return;}try{std::filesystem::create_directories(_dir_path_name);}catch (const std::filesystem::filesystem_error &e){std::cerr << e.what() << "\r\n";}}~FileLogStrategy(){}virtual void SyncLog(const std::string &logmessage) override{{LockGuard lock(&_lock);std::string target = _dir_path_name;target += '/';target += _filename;std::ofstream out(target.c_str(), std::ios::app);if (!out.is_open()){return;}out << logmessage << "\n";out.close();}}private:std::string _dir_path_name;std::string _filename;Mutex _lock;
};class Logger
{
public:Logger(){}void EnableConsoleStrategy(){_strategy = std::make_unique<ConsoleLogStrategy>();}void EnableFileStrategy(){_strategy = std::make_unique<FileLogStrategy>();}class LogMessage{public:LogMessage(LoggerLevel level, std::string filename, int line, Logger& logger): _curr_time(GetCurrentTime()), _level(level), _pid(getpid()), _filename(filename), _line(line), _logger(logger){std::stringstream ss;ss << "[" << _curr_time << "] "<< "[" << LoggerLevelToString(_level) << "] "<< "[" << _pid << "] "<< "[" << _filename << "] "<< "[" << _line << "]"<< " - ";_loginfo = ss.str();}template <typename T>LogMessage &operator<<(const T &info){std::stringstream ss;ss << info;_loginfo += ss.str();return *this;}~LogMessage(){if (_logger._strategy){_logger._strategy->SyncLog(_loginfo);}}private:std::string _curr_time; // 时间戳LoggerLevel _level;     // 日志等级pid_t _pid;             // 进程pidstd::string _filename;  // 文件名int _line;              // 行号std::string _loginfo;   // 一条合并完成的,完整的日志信息Logger &_logger;        // 提供刷新策略的具体做法};LogMessage operator()(LoggerLevel level, std::string filename, int line){return LogMessage(level, filename, line, *this);}~Logger(){}private:std::unique_ptr<LogStrategy> _strategy;
};Logger logger;#define LOG(level) logger(level, __FILE__, __LINE__)
#define EnableConsoleStrategy() logger.EnableConsoleStrategy()
#define EnableFileStrategy() logger.EnableFileStrategy()

Mutex.hpp

#pragma once
#include<iostream>
#include<mutex>
#include<pthread.h>class Mutex
{
public:Mutex(){pthread_mutex_init(&_lock, nullptr);}void Lock(){pthread_mutex_lock(&_lock);}void Unlock(){pthread_mutex_unlock(&_lock);}~Mutex(){pthread_mutex_destroy(&_lock);}
private:pthread_mutex_t _lock;
};class LockGuard
{
public:LockGuard(Mutex* _mutex):_mutexp(_mutex){_mutexp->Lock();}~LockGuard(){_mutexp->Unlock();}
private:Mutex* _mutexp;
};

main.cc

#include"Logger.hpp"int main()
{EnableConsoleStrategy();LOG(LoggerLevel::ERROR) << "hello linux" << ", 6.66 " << 123;LOG(LoggerLevel::WARNING) << "hello linux" << ", 6.66 " << 123;LOG(LoggerLevel::ERROR) << "hello linux" << ", 6.66 " << 123;LOG(LoggerLevel::ERROR) << "hello linux" << ", 6.66 " << 123;LOG(LoggerLevel::ERROR) << "hello linux" << ", 6.66 " << 123;// std::string test = "hello world, hello log";// std::unique_ptr<LogStrategy> logger_ptr = std::make_unique<ConsoleLogStrategy>();// // logger_ptr->SyncLog(test);// // std::unique_ptr<LogStrategy> logger_ptr = std::make_unique<FileLogStrategy>();// logger_ptr->SyncLog(GetCurrentTime());// sleep(1);// logger_ptr->SyncLog(GetCurrentTime());// sleep(1);// logger_ptr->SyncLog(GetCurrentTime());// sleep(1);// logger_ptr->SyncLog(GetCurrentTime());// sleep(1);// logger_ptr->SyncLog(GetCurrentTime());return 0;
}

Makefile

logger_test:main.ccg++ -o $@ $^ -std=c++17 -lpthread
.PHONY:clean
clean:rm -f logger_test

这里的构思非常的巧妙,本来想要输出一条完整的日志信息,需要很复杂的操作,现在利用这样的做法就可以用一行代码输出一条完整的日志信息。

下面我们就来看看是怎样的做法呢?

在这里插入图片描述

我们在外部类 Logger 里重载了运算符(),返回了一个 LogMessage 类的临时对象

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

EnableConsoleStrategy();这个其实就是一个宏,这个宏是对于 Logger 类里面的两个函数的简便操作,LOG也是一个宏,是对于()运算符的重载函数的简便操作

在这里插入图片描述

所以,当调用了LOG宏之后会返回一个临时对象,<<运算符重载函数是LogMessage类的一个成员函数,返回的是临时对象的引用,因为,LOG宏返回一个LogMessage类的临时对象,这个临时对象又继续调用了 << 运算符函数,继续返回临时对象的引用,以此类推,直到调用结束

临时对象是具有常性的,它的生命周期在一条语句之后结束,所以可以返回临时对象的引用

今天的文章分享到此结束,觉得不错的伙伴给个一键三连吧。

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

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

相关文章

conda 创建环境嵌套报错

使用conda create --prefix /path可以成功创建&#xff0c;有可能时默认路径冲突导致的 conda config --show 发现&#xff1a; envs_dirs: /root/autodl-tmp/miniconda3/envs/envs_test/path/root/autodl-tmp/miniconda3/envs/root/.conda/envs 未显式指定环境路径&#xf…

低代码核心原理总结

Web 低代码平台核心原理深度解析 1. 架构总览 Web低代码平台的核心架构包含四个关键层次&#xff1a; class LowCodePlatform {constructor() {this.visualEditor new VisualEditor(); // 可视化编辑器this.metaDataEngine new MetaDataEngine(); // 元数据引擎this.code…

操作系统研发工作心得体会 - 于复杂性中构建秩序

在操作系统&#xff08;OS&#xff09;研发这片要求极致严谨与创新的工程深海中航行数载&#xff0c;我的角色从一个纯粹的技术专家&#xff0c;逐渐演变为一个需要兼顾技术深度、系统广度与团队效能的复合型角色。这段旅程&#xff0c;让我深刻体会到&#xff0c;构建一个成功…

Excel 表格 - Excel 减少干扰、专注于内容的查看方式

Excel 减少干扰、专注于内容的查看方式 1、隐藏元素 点击 【视图】 -> 取消勾选 【网格线】 -> 取消勾选 【编辑栏】 -> 取消勾选 【标题】2、全屏显示 点击 【功能区显示选项】&#xff08;工具栏右下角小箭头&#xff09; -> 点击 【全屏模式】

C# Web API 前端传入参数时间为Utc

Web API 前端传入参数时间为Utc&#xff08;时间相差8个小时&#xff09;1.在Program.csbuilder.Services.AddControllers().AddJsonOptions(options > {// 序列化时将时间转换为本地时间&#xff08;北京时间&#xff09;options.JsonSerializerOptions.Converters.Add(new…

AI Agent开发入门:Semantic Kernel构建智能邮件助手

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;H卡级别算力&#xff0c;80G大显存&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生更享专属优惠。 引言&#xff1a;AI Agent——下一代人机交互范式 在人工智能技术…

WebAssembly:开启高性能 Web 应用的新篇章

在互联网技术飞速发展的浪潮中&#xff0c;Web应用的性能一直是一个重要的优化目标。传统的JavaScript虽然灵活便捷&#xff0c;但在处理CPU密集型任务时&#xff0c;其性能瓶颈日益凸显&#xff0c;限制了Web应用在游戏、音视频编辑、科学计算、图像处理等高性能领域的深入发展…

001-003 产品经理-ML应用构建-ML应用范围

001-003 产品经理-ML应用构建-ML应用范围 时间&#xff1a;2025年09月08日14:48:01 备注&#xff1a;笔记回顾和复习&#xff0c;仅用于分享而非商用&#xff0c;引用内容若侵权请联系并删除。 文章目录001-003 产品经理-ML应用构建-ML应用范围导引 学习法则1 内容索引 产品经…

软件测试错题笔记

1.capitalize()表示将字符串第一个字符转换为大写 2.pop()方法&#xff1a;指定一个键&#xff08;key&#xff09;作为参数来删除并返回对应的值&#xff0c;不传入任何参数报错。 3.测试方法&#xff1a;黑盒测试&#xff08;等价类划分法、边界值分析、因果图分析&#xf…

【一文分享】安全数据交换系统是什么?哪款产品性价比高?

随着数据价值的提升&#xff0c;其流动过程中的安全风险也与日俱增。内部核心数据泄露、外部攻击、不合规传输导致的合规风险……这些问题如同悬在企业头上的“达摩克利斯之剑”。正是在这样的背景下&#xff0c;安全数据交换系统 应运而生&#xff0c;成为了保障数据安全流动的…

postgresql9.2.4 离线安装

1、创建用户[rootvkeep ~]# groupadd postgres [rootvkeep ~]# useradd -g postgres postgres -m -s /bin/bash [rootvkeep ~]# echo "Database123" | passwd --stdin postgres2、安装依赖包[rootvkeep ~]# yum install gcc gcc-c zlib-devel readline readline-deve…

【C++设计模式】第三篇:观察者模式(别名:发布-订阅模式、模型-视图模式、源-监听器模式)

C设计模式系列文章目录 【C设计模式】第一篇 C单例模式–懒汉与饿汉以及线程安全 【C设计模式】第二篇&#xff1a;策略模式&#xff08;Strategy&#xff09;–从基本介绍&#xff0c;内部原理、应用场景、使用方法&#xff0c;常见问题和解决方案进行深度解析 【C设计模式】…

运作管理学习笔记5-生产和服务设施的选址

运作管理-北京交通大学5.1.设施选址概述 设施选址是一个战略性的决策&#xff0c;做这个决策的时候会投入比较多的资源&#xff0c;而且未来去改变选址的成本和代价也比较大。 5.1.1.设施选址的重要性 设施选址影响企业经营情况 设施选址对设施布局以及投产后的生产经营费用、产…

JUnit 详解

一、JUnit 简介&#xff1a;什么是 JUnit&#xff1f;为什么要用它&#xff1f;1.1 核心定义JUnit 是一个开源的、基于 Java 语言的单元测试框架&#xff0c;最初由 Erich Gamma (GoF 设计模式作者之一) 和 Kent Beck (极限编程创始人) 在 1997 年共同开发。作为 xUnit 测试框架…

数据结构造神计划第三天---数据类型

&#x1f525;个人主页&#xff1a;寻星探路 &#x1f3ac;作者简介&#xff1a;Java研发方向学习者 &#x1f4d6;个人专栏&#xff1a;《从青铜到王者&#xff0c;就差这讲数据结构&#xff01;&#xff01;&#xff01;》、 《JAVA&#xff08;SE&#xff09;----如此简单&a…

AI API Tester体验:API测试工具如何高效生成接口测试用例、覆盖异常场景?

前阵子帮后端测试支付接口时&#xff0c;我算是彻底明白 “API 测试能磨掉半条命”—— 明明接口文档里写了十几种参数组合&#xff0c;手动写测试用例时要么漏了 “签名过期” 的场景&#xff0c;要么忘了校验 “金额超过限额” 的返回值&#xff0c;测到半夜还被开发吐槽 “你…

音频驱动数字人人脸模型

1.LatentSync: Taming Audio-Conditioned Latent Diffusion Models for Lip Sync with SyncNet Supervision 字节 2024 文章地址&#xff1a;https://arxiv.org/pdf/2412.09262 代码地址&#xff1a;https://github.com/bytedance/LatentSync 训练推理都有 2.wan2.2-s2v …

CentOS部署ELK Stack完整指南

文章目录&#x1f680; ELK Stack 部署详解&#xff08;CentOS 7/8&#xff09;&#x1f4e6; 一、环境准备1. 关闭防火墙&#xff08;或开放端口&#xff09;2. 关闭 SELinux3. 安装基础依赖4. 验证 Java&#x1f53d; 二、下载并安装 ELK 组件1. 导入 Elastic GPG 密钥2. 创建…

Spring Boot 拦截器(Interceptor)与过滤器(Filter)有什么区别?

在 Spring Boot 项目中&#xff0c;我们经常会遇到需要在请求处理前后执行一些通用逻辑的场景&#xff0c;比如记录日志、权限校验、全局异常处理等。此时&#xff0c;我们通常会面临两种选择&#xff1a;过滤器&#xff08;Filter&#xff09; 和 拦截器&#xff08;Intercept…

【技术教程】如何将文档编辑器集成至基于Java的Web应用程序

在如今的企业协作场景中&#xff0c;“文档” 早已不是简单的文字载体&#xff01;从项目需求文档的多人实时修改&#xff0c;到财务报表的在线批注&#xff0c;再到合同草案的版本追溯&#xff0c;用户越来越需要在 Web 应用内直接完成 “编辑 - 协作 - 存储” 全流程。 但很…