【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;
}

执行结果:

在这里插入图片描述

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

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

相关文章

【Linux】进程的优先级

我们都知道进程等待需要cpu处理的&#xff0c;那就需要一个数据结构来记录要被cpu处理的进程&#xff0c;那这些进程是按一个什么样的方式在这个结构中进行等待呢&#xff1f;下面就要谈到进程的优先级了&#xff1a; 目录 一、进程的优先级的概念 二、查看进程的优先级 2.1…

【javaweb】学习日记Day8 - Mybatis入门 Mysql 多表查询 事务 索引

之前学习过的SQL语句笔记总结戳这里→【数据库原理与应用 - 第六章】T-SQL 在SQL Server的使用_Roye_ack的博客-CSDN博客 【数据库原理与应用 - 第八章】数据库的事务管理与并发控制_一级封锁协议_Roye_ack的博客-CSDN博客 目录 一、多表查询 1、概述 &#xff08;1&#…

Spring-Kafka生产者源码分析

文章目录 概要初始化消息发送小结 概要 本文主要概括Spring Kafka生产者发送消息的主流程 代码准备&#xff1a; SpringBoot项目中maven填加以下依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent&…

大数据技术原理与应用学习笔记第1章

黄金组合访问地址&#xff1a;http://dblab.xmu.edu.cn/post/7553/ 1.《大数据技术原理与应用》教材 官网&#xff1a;http://dblab.xmu.edu.cn/post/bigdata/ 2.大数据软件安装和编程实践指南 官网林子雨编著《大数据技术原理与应用》教材配套大数据软件安装和编程实践指…

Liunx系统编程:信号量

一. 信号量概述 1.1 信号量的概念 在多线程场景下&#xff0c;我们经常会提到临界区和临界资源的概念&#xff0c;如果临界区资源同时有多个执行流进入&#xff0c;那么在多线程下就容易引发线程安全问题。 为了保证线程安全&#xff0c;互斥被引入&#xff0c;互斥可以保证…

Java-泛型

文章目录 Java泛型什么是泛型&#xff1f;在哪里使用泛型&#xff1f;设计出泛型的好处是什么&#xff1f;动手设计一个泛型泛型的限定符泛型擦除泛型的通配符 结论 Java泛型 什么是泛型&#xff1f; Java泛型是一种编程技术&#xff0c;它允许在编译期间指定使用的数据类型。…

操作视频的开始与暂停

调用 ref.current.play() 方法来播放视频&#xff1b; 如果视频需要暂停&#xff0c;我们调用 ref.current.pause() 方法来暂停视频。 通过 useRef 创建的 ref 操作视频的开始与暂停 当用户点击按钮时&#xff0c;根据当前视频的状态&#xff0c;我们会开始或暂停视频&…

ip地址、LINUX、与虚拟机

子网掩码&#xff0c;是用来固定网络号的&#xff0c;例如255&#xff0c;255,255,0&#xff0c;表明前面三段必须为网络号&#xff0c;后面必须是主机号&#xff0c;那么怎么实现网络复用呢&#xff0c;例如使用c类地址&#xff0c;但是正常子网掩码是255&#xff0c;255,255,…

GB28181学习(二)——注册与注销

概念 使用REGISTER方法进行注册和注销&#xff1b;注册和注销应进行认证&#xff0c;认证方式应支持数字摘要认证方式&#xff0c;高安全级别的宜支持数字证书认证&#xff1b;注册成后&#xff0c;SIP代理在注册过期时间到来之前&#xff0c;应向注册服务器进行刷新注册&…

vue从零开始学习

npm install慢解决方法:删掉nodel_modules。 5.0.3:表示安装指定的5.0.3版本 ~5.0.3:表示安装5.0X中最新的版本 ^5.0.3: 表示安装5.x.x中最新的版本。 yarn的优点: 1.速度快,可以并行安装 2.安装版本统一 项目搭建: 安装nodejs查看node版本:node -v安装vue clie : np…

C语言练习8(巩固提升)

C语言练习8 编程题 前言 奋斗是曲折的&#xff0c;“为有牺牲多壮志&#xff0c;敢教日月换新天”&#xff0c;要奋斗就会有牺牲&#xff0c;我们要始终发扬大无畏精神和无私奉献精神。奋斗者是精神最为富足的人&#xff0c;也是最懂得幸福、最享受幸福的人。正如马克思所讲&am…

明厨亮灶监控实施方案 opencv

明厨亮灶监控实施方案通过pythonopencv网络模型图像识别算法&#xff0c;一旦发现现场人员没有正确佩戴厨师帽或厨师服&#xff0c;及时发现明火离岗、不戴口罩、厨房抽烟、老鼠出没以及陌生人进入后厨等问题生成告警信息并进行提示。OpenCV是一个基于Apache2.0许可&#xff08…

01-虚拟机安装Windows Server操作系统

1、创建并配置虚拟机 2、安装操作系统 找到windows Server镜像 等待安装 3、设置密码

数据结构之单链表java实现

基本概念 链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中指针链接次序实现的。和数组相比较&#xff0c;链表不需要指定大小&#xff0c;也不需要连续的地址。 单链表的基本设计思维是&#xff0c;利用结构体的设置&#xff0c…

滑动窗口实例5(水果成篮)

题目&#xff1a; 你正在探访一家农场&#xff0c;农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示&#xff0c;其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多地收集水果。然而&#xff0c;农场的主人设定了一些严格的规矩&#xff0c;你必须按…

垃圾回收 - 引用计数法

GC原本是一种“释放怎么都无法被引用的对象的机制”。那么人们自然而然就会想到&#xff0c;可以让所有对象事先记录下“有多少程序引用了自己”。让各对象知道自己的“人气指数”&#xff0c;从而让没有人气的对象自己消失&#xff0c;这就是引用计数法。 1、计数器 计数器表…

使用MATLAB解算炼油厂的选址

背景 记得有一年的数据建模大赛&#xff0c;试题是炼油厂的选址&#xff0c;最后我们采用MATLAB编写&#xff08;复制&#xff09;蒙特卡洛算法&#xff0c;还到了省级一等奖&#xff0c;这里把仅有一些记忆和材料&#xff0c;放到这里来&#xff0c;用来纪念消失的青春。 本…

selenium可以编写自动化测试脚本吗?

Selenium可以用于编写自动化测试脚本&#xff0c;它提供了许多工具和API&#xff0c;可以与浏览器交互&#xff0c;模拟用户操作&#xff0c;检查网页的各个方面。下面是一些步骤&#xff0c;可以帮助你编写Selenium自动化测试脚本。 1、安装Selenium库和浏览器驱动程序 首先…

Redis布隆过滤器原理

其实布隆过滤器本质上要解决的问题&#xff0c;就是防止很多没有意义的、恶意的请求穿透Redis&#xff08;因为Redis中没有数据&#xff09;直接打入到DB。它是Redis中的一个modules&#xff0c;其实可以理解为一个插件&#xff0c;用来拓展实现额外的功能。 可以简单理解布隆…

Educational Codeforces Round 154 (Rated for Div. 2)

Educational Codeforces Round 154 (Rated for Div. 2) A. Prime Deletion 思路&#xff1a; 因为1到9每个数字都有&#xff0c;所以随便判断也质素即可 代码 #include<bits/stdc.h> using namespace std; #define int long long #define rep(i,a,n) for(int ia;i<…