Linux环境之----POSIX信号量

news/2025/9/27 18:56:23/文章来源:https://www.cnblogs.com/slgkaifa/p/19115479

Linux环境之----POSIX信号量

1.基本概念

信号量(Semaphore):信号量是一个非负整数,用于控制对共享资源的访问。它的值表示可用资源的数量。信号量的操作主要有两种:

P操作(Wait操作):将信号量的值减1。如果信号量的值小于0,则进程阻塞,等待信号量的值变为非负。

V操作(Signal操作):将信号量的值加1。如果信号量的值大于0,则唤醒一个等待该信号量的进程。

2.相关函数

2.1 sem_init()函数

该函数初始化信号量

参数说明:

sem:信号量的指针。

pshared:是否跨进程共享,0表示仅在当前进程的线程间共享,非0表示跨进程共享。

value:信号量的初始值。

2.2 sem_destroy()函数

该函数销毁信号量

返回值:成功为0,失败为-1

2.3 sem_wait()函数

该函数用于对信号量执行 P 操作(Wait 操作)。它会将信号量的值减 1,如果信号量的值小于 0,则调用该函数的线程或进程会阻塞,直到信号量的值变为非负。

2.4 sem_post()函数

该函数用于对信号量执行 V 操作(Signal 操作)。它会将信号量的值加 1,如果信号量的值大于 0,则会唤醒一个等待该信号量的线程或进程。

3.环形队列

3.1 环形队列复习

这里仅列出一张老师的板书图片作为复习~

3.2 321CP场景

简单来说就是三种情况:

首先假设生产者消费者此时处于同一位置上

1)当队列为空的时候,必须让生产者先运行,要不然消费者访问不到数据

2)当队列为满的时候,必须让消费者先运行,否则会造成数据的丢失,因为新生产的数据会覆盖        原有数据

3)当队列不为满并且不为空的时候,并且如果此时队首!=队尾,那么此时访问的一定不是同一个        位置,此时,就可以并发执行了

4.代码练习(环形队列实现PC模型)

思路梳理:

要实现环形队列,并且让生产者消费者都在上面跑,那么我们就要首先保证生产者在前,消费者在后,这样才能保证线程不会崩溃,而且二者不能有"扣圈“现象的出现,之后在访问时,还要保证生产者和消费者在访问时,队列有足够的空间以及数据,这里就要用到信号量的PV操作了,在确定有足够位置的时候,就可以加锁解锁进行数据交换了~,因此我们代码将分为如下几个文件进行封装!

4.1 Mutex.hpp

这个就是之前互斥锁那里的封装,不多说了~

#pragma once
#include
#include
#include
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
pthread_mutex_t *Get()
{
return &_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;
};

4.2 Sem.hpp

#pragma once
#include
#include
class Sem
{
public:
Sem(int num):_initnum(num)
{
sem_init(&_sem,0,_initnum);
}
void P()
{
int n=sem_wait(&_sem);
(void)n;    //防止警报
}
void V()
{
int n=sem_post(&_sem);
(void)n;
}
~Sem()
{
sem_destroy(&_sem);
}
private:
sem_t _sem;
int _initnum;
};

4.3 RingQueue.hpp

#pragma once
#include
#include "Sem.hpp"
#include "Mutex.hpp"
#include
using namespace std;
static int gcap = 5; // for debug
template
class RingQueue
{
public:
RingQueue(int cap=gcap)
:_cap(cap)
,_ring_queue(cap)
,_space_sem(cap)
,_data_sem(0)
,_p_step(0)
,_c_step(0)
{}
void Pop(T* out)                   //出队列
{
_data_sem.P();     //调用数据信号量的P操作(等待),确保队列中有足够的数据可以被取出。
{
LockGuard lockguard(&_c_lock);    //先上锁
*out=_ring_queue[_c_step++];    //把要出去的数据给out,之后消费者向后走
_c_step%=_cap;             //保证一直在这个环形队列里面
}
_space_sem.V();     //调用空间信号量的V操作(释放),表示队列中的空间已经释放,可以用于新的元素入队。
}
void Enqueue(const T&in)            //入队列
{
_space_sem.P();//调用空间信号量的P操作(等待),确保队列中有足够的空间可以写入新元素。
{
LockGuard lockguard(&_p_lock);      //先上锁
// 生产数据了!有空间,在哪里啊??
_ring_queue[_p_step++]=in;         //把当前生产者位置填上,之后让生产者向后走
// 维持环形特点
_p_step%=_cap;
}
_data_sem.V();//调用数据信号量的V操作(释放),表示队列中的数据已经增加,可以被取出。
}
~RingQueue()
{}
private:
vector _ring_queue; // 临界资源
int _cap;
Sem _space_sem;
Sem _data_sem;
// 生产和消费的位置
int _p_step;
int _c_step;
// 两把锁
Mutex _p_lock;
Mutex _c_lock;
};

4.4 main.cc

#include
#include
#include"RingQueue.hpp"
using namespace std;
void* consumer(void* args)
{
RingQueue *rt=static_cast*>(args);
while(true)
{
int data=0;
rt->Pop(&data);        //取出下标为0的元素
cout *rt=static_cast*>(args);
int data=1;
while (1)
{
sleep(1);
rt->Enqueue(data);     //生产的数据入队列,并且要入在消费者前面
cout *rq=new RingQueue();
pthread_t c[2],p[3];
pthread_create(c,nullptr,consumer,(void*)rq);
pthread_create(c+1,nullptr,consumer,(void*)rq);
pthread_create(p,nullptr,prducer,(void*)rq);
pthread_create(p+1,nullptr,prducer,(void*)rq);
pthread_create(p+2,nullptr,prducer,(void*)rq);
pthread_join(c[0],nullptr);
pthread_join(c[1],nullptr);
pthread_join(p[0],nullptr);
pthread_join(p[1],nullptr);
pthread_join(p[2],nullptr);
delete rq;
return 0;
}

运行:

模型完成!!!

5. 日志打印

下面我们进行一组扩展练习,打印一组日志,

5.1 时间的获取

这里就不得不用到时间戳了,为了获取时间戳我们就要调用一下time函数,函数原型如下:

5.2 当地时间的转化

获得时间后,还要转化为当地时间:

5.3 日志的刷新和打印

我们总共又两种刷新方式可以选择,第一种是向显示器文件刷新,第二种是向目标文件中刷新,如果要是构造两个类的话,有点过于麻烦了,因为大多数东西都是相同的啊,因此,我们想到了C++提供了基类和派生类,那么我们可以利用这一点,定义一个“刷新”的基类,之后设置一个刷新的虚函数,之后再派生类中进行函数重写不就可以了吗!!!!因此我们可以分别搞这两个派生类了。

首先是向屏幕输出,这个没啥,就一个cout就完事了,这个就不多说了~

再看一下向目标文件输出,那我首先就要检查一下文件是否存在!存在的话就要返回,不存在就要创建,为此,在创建的时候我们可以尝试用try....catch结构,以防创建失败!创建完成后,就向这个文件里面写入就好了~可以用输出文件流向这个文件中写入,并且以追加的方式向文件中写入,保证写入的数据会被添加到文件的末尾而不是覆盖原有内容。

最后我们再用网络刷新把这两个包括一下,可以定义一个内部类

ps:此想法源于google日志代码!我看到比较好后学习了过来,如果有讲的不到位或者错误的地方欢迎指出~~

参考代码:

这是logger.hpp文件

#pragma once
#include
#include
#include  //C++17 文件操作
#include
#include
#include
#include
#include
#include "Mutex.hpp"
using namespace std;
// 规定出场景的日志等级
enum class LOGLEVEL
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
string Level2String(LOGLEVEL level)
{
switch (level)
{
case LOGLEVEL::DEBUG:
return "DEBUG";
case LOGLEVEL::ERROR:
return "ERROR";
case LOGLEVEL::FATAL:
return "FATAL";
case LOGLEVEL::INFO:
return "IOFO";
case LOGLEVEL::WARNING:
return "WARNING";
default:
return "UNKNOWN";
}
}
// 打印当前日期  20XX-09-25 19:10:20
string GetCurTime()
{
// 1. 获取时间戳
time_t curtime = time(nullptr);
// 2. 如何把时间戳转换成为20XX-08-04 12:27:03
struct tm currtm;
localtime_r(&curtime, &currtm);
// 3. 转换成为字符串 -- dubug?
char timebuffer[64];
snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
currtm.tm_year + 1900, // tm_year 成员表示自1900年以来的年数
currtm.tm_mon + 1,     // tm_mon 成员表示月份,范围是0到11,
currtm.tm_mday,
currtm.tm_hour,
currtm.tm_min,
currtm.tm_sec);
return timebuffer;
}
// 1. 刷新的问题 -- 假设我们已经有了一条完整的日志,string->设备(显示器,文件)
// 基类方法
class LogStrategy
{
public:
virtual ~LogStrategy() = default;
// virtual关键字用于实现多态性,即允许派生类(子类)重写基类(父类)的虚函数。
// 虚函数是定义在基类中,并且在派生类中被重写的函数。
//= default; 表示这个析构函数将由编译器自动生成默认的实现。
virtual void SyncLog(const string &logmessage) = 0;
// 这个函数是一个纯虚函数(pure virtual function),它没有具体的实现(即函数体为空),并且被声明为= 0;
// 纯虚函数是抽象类的一部分,抽象类是不能被实例化的,具体的实现将由派生类提供.
};
// 显示器刷新
class ConsoleLogStrategy : public LogStrategy
{
public:
~ConsoleLogStrategy()
{
}
void SyncLog(const string &logmessage) override
{
LockGuard lockguard(&_lock);
cout  log/hello.log
Mutex _lock;
};
// 网络刷新
// 1. 定制刷新策略
// 2. 构建完整的日志
class Logger
{
public:
Logger()
{
}
void EnableConsoleLogStrategy()
{
_strategy = make_unique();
}
void EnableFileLogStrategy()
{
_strategy = std::make_unique();
}
// 形成一条完整日志的方式
class LogMessage
{
public:
LogMessage(LOGLEVEL level, std::string &filename, int line, Logger &logger)
: _curr_time(GetCurTime()),
_level(level),
_pid(getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
std::stringstream ss;
ss
LogMessage &operatorSyncLog(_loginfo);
}
}
private:
string _curr_time; // 日志时间
LOGLEVEL _level;   // 日志等级
pid_t _pid;        // 进程pid
string _filename;
int _line;
string _loginfo; // 一条合并完成的,完整的日志信息
Logger &_logger; // 提供刷新策略的具体做法
};
LogMessage operator()(LOGLEVEL level,string filename,int line)
{
return LogMessage(level,filename,line,*this);
}
~Logger()
{
}
private:
unique_ptr _strategy;
};
Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
#define EnableConsoleLogStrategy() logger.EnableConsoleLogStrategy()
#define EnableFileLogStrategy() logger.EnableFileLogStrategy()

这是Mutex.hpp文件

#pragma once
#include
#include
#include
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
pthread_mutex_t *Get()
{
return &_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
#include "Loger.hpp"
int main()
{
EnableConsoleLogStrategy();
string test = "hello world, hello log";
//  测试策略1:显示器写入
std::unique_ptr logger_ptr = std::make_unique();
logger_ptr->SyncLog(test);
logger_ptr->SyncLog(test);
logger_ptr->SyncLog(test);
logger_ptr->SyncLog(test);
return 0;
}

运行结果:

向文件中写入:

#include
#include "Loger.hpp"
int main()
{
EnableConsoleLogStrategy();
string test = "hello world, hello log";
//测试策略2:文件写入
unique_ptr logger_ptr=make_unique();
logger_ptr->SyncLog(GetCurTime());
sleep(1);
logger_ptr->SyncLog(GetCurTime());
sleep(1);
logger_ptr->SyncLog(GetCurTime());
sleep(1);
logger_ptr->SyncLog(GetCurTime());
sleep(1);
logger_ptr->SyncLog(GetCurTime());
sleep(1);
return 0;
}

运行结果:

好,本篇博客到此结束,下一篇博客我们来设计线程池~

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

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

相关文章

WPF Prism register interface and service, view and viewmodel, IRegionManager, RequestNavigate

Install-Package Prism.DryIOC; Install-Package Prism.Wpf;Install-Package Microsoft.Xaml.Behaviors.WPF; //App.xaml <prism:PrismApplication x:Class="WpfApp14.App"xmlns="http://schemas.m…

完整教程:Flink 容错从状态后端到 Exactly-Once

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

门户网站属于新媒体吗台州网站建设慕枫

莆田学院录取分数线2020是多少分&#xff0c;各专业录取分数线是多少&#xff0c;是每个填报莆田学院的考生最关注的问题&#xff0c;随着各省高考录取批次相继公布&#xff0c;考生也开始关心是否被录取&#xff0c;本站小编整理相关信息供参考&#xff0c;仅供参考。一、莆田…

兼职做网站赚钱吗网站首页设计图片简约

ChatGPT Admin Web 在团队和组织内共享使用人工智能的一站式解决方案。 ​编辑 ​编辑 ​编辑 CAW 是一个自托管网络应用程序&#xff0c;提供开箱即用的用户管理&#xff0c;包括后台界面以及可配置的支付计划和相关支付界面。 GitHub Sponsor / 爱发电 功能 Features …

【08】海康相机C#开发——在海康MVS的**C#实例中添加控件报错**“`不能在本地化模式下添加组件。在 Language 属性中选择”(默认)”以返回到默认格式,然后添加组件`” - 实践

【08】海康相机C#开发——在海康MVS的**C#实例中添加控件报错**“`不能在本地化模式下添加组件。在 Language 属性中选择”(默认)”以返回到默认格式,然后添加组件`” - 实践2025-09-27 18:47 tlnshuju 阅读(0) 评…

哪个建立网站好个人网上卖货的平台

&#x1f497;个人主页&#x1f497; ⭐个人专栏——数据结构学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 导读&#xff1a; 我们在前面学习了单链表和顺序表&#xff0c;以及栈和队列。 今天我们来学习小堆。 关注博主或是订阅专栏&a…

# Windows CMD 基本指令参考手册

Windows CMD 基本指令参考手册常用命令提示符指令速查指南,包含直接复制功能Windows 命令提示符(CMD)是一个强大的工具,可以让您通过输入命令来执行各种系统操作。本参考手册整理了最常用的 CMD 指令,方便您快速查…

P13019 [GESP202506 八级] 树上旅行

解题思路 这个问题需要在有根树上模拟移动操作,但直接模拟会超时(因为移动次数可能很大)。核心思想是使用二进制提升(Binary Lifting)技术来优化移动过程。 关键观察:向上移动(移动到父节点):可以使用倍增表 …

完整教程:负载均衡式的在线OJ项目编写(二)

完整教程:负载均衡式的在线OJ项目编写(二)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "…

Java语法基础课程动手动脑及课后实验问题整理文档

一、编程思维与项目分解相关按照“将整个程序分成若干个组件(分解);将多次出现的相似功能设成独立的方法(模式识别、抽象、算法);调试每个独立组件的健壮性(单元测试);按照功能要求进行组件组合(整合);再测…

安装包制作流程-final

1.给项目exe文件添加 管理员权限添加清单文件:在 Visual Studio 中,右键点击你的 WPF 项目,选择“添加” > “新建项”,然后选择“应用程序清单文件 (app.manifest)”并添加。如果已存在该文件,此步骤可省略。…

让YOLO飞起来:从CPU到GPU的配置指南

最近在配置YOLO(You Only Look Once)进行物体检测和图像分割任务时,发现默认安装的情况下,YOLO使用的是CPU进行计算。 这对于需要处理大量图像或实时检测的任务来说,效率明显不足。 本文将详细介绍如何将YOLO从CP…

记录这辈子见到的第一道从上到下的树上倍增

这道题先是浪费我半个下午做,做不出来有时好久看题解实现,气死我了。 题意。 给定一张 \(N\) 点的树,让我们考虑断掉每一条边,统计分裂出的两个子树的重心编号和之和。 要求 \(O(nlogn)\) 或更优的时间复杂度。 做…

忘形篇

忘形篇先想想暴力怎么做

06.容器存储 - 教程

06.容器存储 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "C…

fm网站开发有源码怎么搭建网站

文章来源&#xff1a;http://blog.csdn.net/edeed/archive/2006/02/10/596271.aspx 1、安装PD v11.0版 2、由pdm生成建表脚本时&#xff0c;字段超过15字符就发生错误&#xff08;oracle&#xff09; 原因未知&#xff0c;解决办法是打开PDM后&#xff0c;会出现Database的菜单…

电子商务网站如何进行维护和推广建设部网站官网挂证通报

ARM32位系统的内存布局图 32位操作系统的内存布局很经典&#xff0c;很多书籍都是以32位系统为例子去讲解的。32位的系统可访问的地址空间为4GB&#xff0c;用户空间为1GB ~ 3GB&#xff0c;内核空间为3GB ~ 4GB。 为什么要划分为用户空间和内核空间呢&#xff1f; 一般处理器…

一般路人向第39次CSP认证

一般路人向第39次CSP认证Q1 第一题十分水,照着他意思来就行了,十来分钟写出来交上去,不行。反复确认直到半个小时,发现交到第二题上去了。 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int m, n; do…

1748:约瑟夫问题

题目 总时间限制: 1000ms 内存限制: 65536kB 描述 约瑟夫问题:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样…

做报名统计的网站微门户网站建设

前面两篇文章记录了 Spring IOC 的相关知识&#xff0c;本文记录 Spring 中的另一特性 AOP 相关知识。 部分参考资料&#xff1a; 《Spring实战&#xff08;第4版&#xff09;》 《轻量级 JavaEE 企业应用实战&#xff08;第四版&#xff09;》 Spring 官方文档 W3CSchool Spri…