【Linux】线程安全-信号量
文章目录
- 信号量原理
- 信号量保证同步和互斥的原理探究
- 信号量相关函数
- 初始化信号量函数
- 等待信号量函数
- 释放信号量函数
- 销毁信号量函数
- 信号量实现生产者消费者模型
信号量原理
信号量的原理:资源计数器 + PCB等待队列 + 函数接口
资源计数器:对共享资源的计数
当执行流获取信号量成功后,信号量当中的计数器减一,如果获取失败,该执行流就会被放入PCB等待队列中
当执行流释放信号量成功之后,信号量当中的计数器会进行加一操作
PCB等待队列:用于存放等待信号量的线程
函数接口:用于操作信号量的一组函数
信号量保证同步和互斥的原理探究
信号量不仅仅可以完成线程之间的同步与互斥,也可以完成进程之间的同步与互斥
互斥原理
1、初始化信号量后,信号量当中的计数器保存的值为1,表示只有一个资源可以被使用
2、当执行流A想要访问共享资源时,首先获取信号量,此时计数器中的值为1,表示可以访问,执行流获取到信号量后,计数器的值从1变成0,执行流A此时去访问共享资源
3、此时,执行流B希望去访问共享资源,首先它要获取信号量,但是信号量中的计数器中的值为0,表示无法获取该信号量,进行无法访问共享资源,因为执行流B的PCB被放进了PCB等待队列中,等待目标信号量的释放,同时,信号量当中的计数器的值进行减一操作,计数器中的值变成了-1,这里的-1表示当前还有1个执行流在等待访问共享资源
同步原理
1、当执行流想要访问共享资源时,首先需要获取信号量
2、如果信号量中的计数器的值大于0,则表示能够获取信号量,进而可以访问共享资源
3、如果信号量中计数器的值小于或等于0,则表示不能获取信号量,进而无法访问共享资源,该执行流被放入PCB等待队列中,同时计数器进行减一操作
4、当释放信号量的时候,会对信号量中计数器进行加一操作
5、如果信号量中的计数器大于0,则唤醒PCB等待队列中的线程
6、如果信号量中的计数器小于或等于0,则不唤醒PCB等待队列中的线程
信号量相关函数
POSIX信号量的函数的名字都是以sem_开头,常用的POSIX信号量函数有以下这些:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, struct timespec*abs_timeout);
sem_t是信号量的类型(sem_t是一个结构体其中有资源计数器和PCB等待队列)
sem_t源码:
typedef union
{char __size[__SIZEOF_SEM_T];long int __align;
} sem_t;
初始化信号量函数
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化一个信号量
参数:
- sem:指向被操作的信号量,传入信号量的地址
- pshared:表示该信号量是用于进程间的还是用于线程间的,填入以下的值
| 数值 | 含义 |
|---|---|
| 0 | 用于线程间,全局变量 |
| 非0 | 用于进程间,将信号量所用到的资源在共享内存当中进行开辟 |
value:资源的个数,本质上就是计数器的值
等待信号量函数
int sem_wait(sem_t *sem);
功能:
执行流调用该函数后,将会对计数器进行减一操作–p操作(P 操作用于获取或锁定信号量),(信号量的计数器自己保证原子性操作不会因为多线程而导致程序计数器中的结果二义性减一操作一步完成。)
如果减一操作后,计数器的值大于0,表示其他执行流仍然可以访问共享资源
如果减一操作后,计数器的值等于0,表示该执行流可以访问共享资源,其他执行流若想访问需要进入PCB等待队列
如果减一操作后,计数器的值小于0,表示当前执行流需要进入PCB等待队列,其他执行流若想访问也需要进入PCB等待队列
参数:sem:指向被操作的信号量,传入信号量的地址
要注意的是,先获取信号量再获取互斥锁,先获取信号量,再保证互斥,就是说,接口一定是先对程序计数器进行减一操作,再拿到锁,假设,如果先拿到锁,再进行信号量减一,那么当拿到锁之后,信号量如果从0减为小于0的数字,那么执行流就会被放到PCB等待队列中去了,这个函数也没有传输互斥锁,所以内部不会进行解锁,所以这时线程就会带着锁进行等待队列,然后无法解锁,锁一直被锁着,其他临界区也无法访问当前临界区资源
释放信号量函数
int sem_post(sem_t *sem);
功能:
执行流调用该函数后,将会对计数器进行加一操作–v操作(V 操作用于释放或解锁信号量)
判断资源计数器的值是否小于等于0
之所以还要判断是否等于0是因为,假设有一个生产者队列和一个消费者队列,当生产者将队列生产满了之后,假设此时程序计数器为-1,而将程序计数器减为-1的那个线程还在等待队列中,此时消费者线程被生产者唤醒,消费数据,出队,它将生产者信号量计数器中加一变成0,那么此时也要通知等待队列中的生产者线程出来工作
是:通知PCB等待队列
否:不用通知PCB等待队列,因为没有线程在等待
参数:sem:指向被操作的信号量,传入信号量的地址
销毁信号量函数
int sem_destroy(sem_t *sem);
功能:销毁目标信号量
参数:sem:指向被操作的信号量,传入信号量的地址
信号量实现生产者消费者模型
代码如下:
#include<pthread.h>
#include<semaphore.h>
#include<stdio.h>
#include<queue>
#include<iostream>
#define CAPACITY 4
#define THREAD_COUNT 2
int g_val = 0;
using namespace std;
class Safe_Queue{
public:Safe_Queue(){capacity_ = CAPACITY;sem_init(&lock_, 0, 1);//锁的信号量资源,要么为0表示不可用,要么为1表示可用sem_init(&cons_sem_, 0, 0);//消费者的信号量,最开始的时候,队列为空,所以消费者没有资源可用sem_init(&prod_sem_, 0, capacity_);//生产者的信号量,最开始的时候,队列为空,所以生产者可用资源数就是队列大小}~Safe_Queue(){sem_destroy(&lock_);sem_destroy(&prod_sem_);sem_destroy(&cons_sem_);}//插入接口-生产者调用void Push(int data){sem_wait(&prod_sem_);//等待信号量,执行过后,对计数器进行减一操作sem_wait(&lock_);//获取访问_que资源_que.push(data);printf("I am product, I product %d\n", data);//sem_post(&cons_sem_);//对消费者可用的资源计数加一sem_post(&lock_);//释放信号量函数,执行过后,对计数器进行加一操作sem_post(&cons_sem_);//对消费者可用的资源计数加一}//获取元素接口-消费者调用int Pop(){sem_wait(&cons_sem_);sem_wait(&lock_);int temp = _que.front();_que.pop();printf("I am consume, I consume %d\n", temp);//sem_post(&prod_sem_);sem_post(&lock_);sem_post(&prod_sem_);return temp;}
private://STL中的queue是线程不安全的,所以需要进行保护queue<int> _que;sem_t lock_;//用来保证队列资源互斥的信号量sem_t prod_sem_;//生产者的信号量sem_t cons_sem_;//消费者的信号量size_t capacity_;//人为约定队列的大小
};
void* cons_start(void* arg){Safe_Queue *q = (Safe_Queue*)arg;while(1){q->Pop();}return NULL;
}
void* prod_start(void* arg){Safe_Queue *q = (Safe_Queue*)arg;int data = 0;while(1){q->Push(data);data++;}return NULL;
}
int main(){Safe_Queue *q = new Safe_Queue();if(q == NULL) return 0;pthread_t cons[THREAD_COUNT], prod[THREAD_COUNT];for(int i=0; i<THREAD_COUNT; ++i){int ret = pthread_create(&prod[i], NULL, prod_start, (void*)q);if(ret < 0){perror("pthread_create");return 0;}ret = pthread_create(&cons[i], NULL, cons_start, (void*)q);if(ret < 0){perror("pthread_create");return 0;}}for(int i=0; i<THREAD_COUNT; ++i){pthread_join(cons[i], NULL);pthread_join(prod[i], NULL);}return 0;
}
执行结果:

相关文章:
【Linux】线程安全-信号量
文章目录 信号量原理信号量保证同步和互斥的原理探究信号量相关函数初始化信号量函数等待信号量函数释放信号量函数销毁信号量函数 信号量实现生产者消费者模型 信号量原理 信号量的原理:资源计数器 PCB等待队列 函数接口 资源计数器:对共享资源的计…...
数字IC验证——PSS可移植测试用例
PSS是Accellera组织定义的测试用例生成规范,其思想是定义一个抽象模型,EDA工具可以从中生成适用于每个设计层次结构和每个验证平台的测试,即PSS定义了统一的测试场景,而场景的使用可以横跨不同验证层次和配置。 这种特性决定了PSS…...
java设计模式---策略模式
策略模式的定义 策略设计模式是一种行为设计模式。当在处理一个业务时,有多种处理方式,并且需要再运行时决定使哪一种具体实现时,就会使用策略模式。 策略模式的类图: 策略模式的实现 在支付业务中,有三种付款方式&…...
5-redis集群搭建安装
1.先决条件 1.1.OS基础配置 CentOS为了能够正常安装redis,需要对CentOS进行常规的一些基础配置,主要有:关闭防火墙与selinux,设置主机名,配置虚拟机IP地址使其能够与外网ping通,配置IP地址与主机名映射,配置yum源。具体配置参见: Linux常规基础配置_小黑要上天的博客…...
(数字图像处理MATLAB+Python)第十一章图像描述与分析-第七、八节:纹理描述和其他描述
文章目录 一:纹理描述(1)联合概率矩阵法A:定义B:基于联合概率矩阵的特征C:程序 (2)灰度差分统计法A:定义B:描述图像特征的参数 (3)行程…...
MySQL提权
参考: mysql提权篇 | Wh0ales Blog MySQL 提权方法整理 - Geekbys Blog MySQL_UDF提权漏洞复现-云社区-华为云 MYSQL UDF手动提权及自动化工具使用_udf提权工具_小直789的博客-CSDN博客 MySQL提权的三种方法 - FreeBuf网络安全行业门户 ......
FPGA优质开源项目 – UDP万兆光纤以太网通信
本文开源一个FPGA项目:UDP万兆光通信。该项目实现了万兆光纤以太网数据回环传输功能。Vivado工程代码结构和之前开源的《UDP RGMII千兆以太网》类似,只不过万兆以太网是调用了Xilinx的10G Ethernet Subsystem IP核实现。 下面围绕该IP核的使用、用户接口…...
如何中mac上安装多版本python并配置PATH
摘要 mac 默认安装的python是 python3,但是如果我们需要其他python版本时,该怎么办呢? 例如:需要python2 版本,如果使用homebrew安装会提示没有python2。同时使用python --version 会发现commond not found。 所以本…...
window 常用基础命令
0、起步 0-1) 获取命令的参数指引 netstat /? 0-2) 关于两个斜杠: window 文件路径中使用反斜杠:\ linux 文件路径中使用:/ 1、开关机类指令 shutdown /s # 关机shutdown /r # 重启shutdown /l …...
lintcode 1815 · 警报器 【simple vip 前缀和数组】
题目 https://www.lintcode.com/problem/1815 一个烟雾警报器会监测len秒内的烟雾值,如果这段时间烟雾值平均值大于k那么警报器会报警。现在给你n个数代表刚开始工作n秒内警报器监测的烟雾值(警报器从第len秒开始判断是否报警),…...
【强化学习】MDP马尔科夫链
基本元素 状态集:表示智能体所处所有状态的全部可能性的集合。类似的集合,行为集,回报集决策:规定我在某个状态下,我做出某个action马尔可夫链:学术上来说是无记忆性质。说白了就是我只在乎我目前的状态。…...
SpringBoot自写项目记录
设置静态资源映射 Slf4j 用来打印日志 Configuration Slf4j //设置静态资源映射 public class WebMvcConfig extends WebMvcConfigurationSupport {Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {log.info("开始静态资源配置");r…...
Windows10上使用llama-recipes(LoRA)来对llama-2-7b做fine-tune
刚刚在Windows10上搭建环境来对llama2做finetune,里面坑还是挺多的,这里把印象中的坑整理了一下以作备忘。 llama-recipes是meta的开源项目,Github地址为:GitHub - facebookresearch/llama-recipes: Examples and recipes for Ll…...
06-限流策略有哪些,滑动窗口算法和令牌桶区别,使用场景?【Java面试题总结】
限流策略有哪些,滑动窗口算法和令牌桶区别,使用场景? 常见的限流算法有固定窗口、滑动窗口、漏桶、令牌桶等。 6.1 固定窗口 概念:固定窗口(又称计算器限流),对一段固定时间窗口内的请求进行…...
2021年06月 C/C++(六级)真题解析#中国电子学会#全国青少年软件编程等级考试
C/C++编程(1~8级)全部真题・点这里 第1题:逆波兰表达式 逆波兰表达式是一种把运算符前置的算术表达式,例如普通的表达式2 + 3的逆波兰表示法为+ 2 3。逆波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如(2 + 3) * 4的逆波兰表示法为* + 2 3 …...
Tuxera NTFS for Mac2023苹果电脑Mac硬盘读写工具
Tuxera NTFS for Mac是一款高效稳定的NTFS读写工具,可以让你在Mac上完整地读写兼容NTFS格式驱动器,对磁盘进行访问、编辑、存储和传输文件等操作。Tuxera NTFS for Mac软件是一款高效稳定的NTFS读写工具,可以让你在Mac上完整地读写兼容NTFS格…...
系统调用的过程
系统调用也是库函数的底层实现,当高级语言代码中如调用了库函数,在编译为机器语言指令后,指令包含前期处理相关命令、传参指令、陷入指令、后续处理相关指令。在执行陷入指令时发生内中断,使CPU进入核心态,执行对系统调…...
Python将多个文件的名称或后缀名由大写字母修改为小写的方法
本文介绍基于Python语言,基于一个大文件夹,遍历其中的多个子文件夹,并对于每一个子文件夹中的大量文件,批量将其文件的名称或后缀名中的字母由大写修改为小写的方法。 本文期望实现的需求为:现有一个大文件夹ÿ…...
Debezium的三种部署方式
Debezium如何部署 debezium 有下面三种部署方式,其中最常用的就是 kafka connect。 kafka connect 一般情况下,我们通过 kafka connect 来部署 debezium,kafka connect 是一个框架和运行时: source connectors:像 debezium 这样将记录发送到 kafka 的source connectors…...
通讯协议057——全网独有的OPC HDA知识一之接口(十二)IOPCHDA_DataCallback
本文简单介绍OPC HDA规范的IOPCHDA_DataCallback(客户端接口)接口方法,更多通信资源请登录网信智汇(wangxinzhihui.com)。 1)HRESULT OnDataChange(dwTransactionID, hrStatus, dwNumItems, pItemValues, phrErrors) 此方法由客…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...
python可视化:俄乌战争时间线关键节点与深层原因
俄乌战争时间线可视化分析:关键节点与深层原因 俄乌战争是21世纪欧洲最具影响力的地缘政治冲突之一,自2022年2月爆发以来已持续超过3年。 本文将通过Python可视化工具,系统分析这场战争的时间线、关键节点及其背后的深层原因,全面…...
使用python进行图像处理—图像滤波(5)
图像滤波是图像处理中最基本和最重要的操作之一。它的目的是在空间域上修改图像的像素值,以达到平滑(去噪)、锐化、边缘检测等效果。滤波通常通过卷积操作实现。 5.1卷积(Convolution)原理 卷积是滤波的核心。它是一种数学运算,…...
[C++错误经验]case语句跳过变量初始化
标题:[C错误经验]case语句跳过变量初始化 水墨不写bug 文章目录 一、错误信息复现二、错误分析三、解决方法 一、错误信息复现 write.cc:80:14: error: jump to case label80 | case 2:| ^ write.cc:76:20: note: crosses initialization…...
qt 双缓冲案例对比
双缓冲 1.双缓冲原理 单缓冲:在paintEvent中直接绘制到屏幕,绘制过程被用户看到 双缓冲:先在redrawBuffer绘制到缓冲区,然后一次性显示完整结果 代码结构 单缓冲:所有绘制逻辑在paintEvent中 双缓冲:绘制…...
