操作系统的信号量操作以及实战中的踩坑分析

往期地址:

  • 操作系统系列一 —— 操作系统概述
  • 操作系统系列二 —— 进程
  • 操作系统系列三 —— 编译与链接关系
  • 操作系统系列四 —— 栈与函数调用关系
  • 操作系统系列五 —— 目标文件详解
  • 操作系统系列六 —— 详细解释【静态链接】
  • 操作系统系列七 —— 装载
  • 操作系统系列八 ——动态链接
  • 操作系统系列九 ——系统调用和API
  • 从编译角度看c和c++混合编译
  • stripped文件描述以及gdb反汇编工具使用

本期主题:

  1. 操作系统的信号量简单讲解以及例子
  2. 踩坑分析

目录

  • 1. 操作系统的信号量是什么?
  • 2. 规范操作方式
  • 3. 踩坑分析


1. 操作系统的信号量是什么?

可以看看我原来写的这篇文章—— UNIX环境编程——信号量与互斥量对比 ,不仅对操作系统的信号量进行了讲解,还对比了信号量和互斥量。这里可以再简单总结一下:

  1. 信号量的作用

在操作系统中,信号量是一种用于控制对共享资源访问的同步机制。它通常用于解决多个进程或线程之间的竞争条件,以及实现互斥访问和同步操作。
信号量具有一个整数值,表示可用资源的数量或者某种状态。根据信号量的值,进程或线程可以执行不同的操作,包括等待(阻塞)、唤醒、以及修改信号量的值等。

  1. 信号量类型

常见的信号量有两种类型:二进制信号量和计数信号量。

  • 二进制信号量: 也称为互斥锁,它的取值范围只能是 0 或 1。它用于实现互斥访问,只允许一个进程或线程访问资源,其他进程或线程必须等待。
  • 计数信号量: 它的取值范围可以是任意非负整数。它用于表示可用资源的数量,例如缓冲区中剩余的可用空间数量,或者允许的并发访问的最大数量。
  1. 信号量的操作

信号量通常提供以下两种操作:

  • P(wait)操作: 用于尝试获取资源。如果资源可用,则执行成功并将信号量的值减一;否则进入等待状态,直到资源可用。
  • V(post)操作: 用于释放资源。执行成功后,将信号量的值加一,表示释放了一个资源。
    信号量的正确使用可以确保对共享资源的安全访问,并解决进程间的竞争条件。因此,信号量是操作系统中重要的同步机制之一。

2. 规范操作方式

信号量的规范操作方式包括以下几个步骤:

  1. 初始化信号量: 在使用信号量之前,首先需要对其进行初始化。初始化操作包括设置信号量的初始值,通常使用 sem_init() 函数来完成。在 POSIX 标准中,通常将信号量初始化为 1(二进制信号量)或者一个正整数(计数信号量)。

  2. 等待操作(P 操作): 在需要访问共享资源之前,先执行等待操作(也称为 P 操作)。等待操作会尝试获取资源,如果资源不可用,则阻塞当前进程或线程,直到资源可用。等待操作通常使用 sem_wait() 函数来完成。

  3. 访问共享资源: 在成功执行等待操作后,进程或线程可以安全地访问共享资源,进行读取、写入或其他操作。

  4. 释放操作(V 操作): 在完成对共享资源的访问后,需要执行释放操作(也称为 V 操作)。释放操作会增加信号量的计数,表示释放了一个资源。释放操作通常使用 sem_post() 函数来完成。

  5. 销毁信号量: 在不再需要使用信号量时,需要对其进行销毁以释放资源。销毁操作通常使用 sem_destroy() 函数来完成。

看一个示例代码,代码实现的功能是:

  1. 创建一个post进程和wait进程
  2. post进程负责把一个全局变量cnt++,并释放信号量,这里的全局变量相当于一个共享区域
  3. wait进程负责等待信号 ,并打印信号量
  4. 预期是能看到post打印一个,wait打印一个

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

#define TEST_TIME	10
int cnt = 0;
sem_t semaphore;

// Post 线程函数
void *post_thread(void *arg) {
	int test_cnt = 0;
	do {
		cnt++;
		sem_post(&semaphore); // 发送信号量
		printf("Send cnt value: %d\n", cnt);
		sleep(1);
		test_cnt++;
	} while(test_cnt < TEST_TIME);
    return NULL;
}

// Wait 线程函数
void *wait_thread(void *arg) {
	int test_cnt = 0;
	do {
		sem_wait(&semaphore); // 等待信号量
		printf("Received cnt value: %d\n", cnt); // 打印接收到的值
		test_cnt++;
		// usleep(1500000);
		sleep(1);
	} while (test_cnt < TEST_TIME);
    return NULL;
}

int main(void) {
    pthread_t post_tid, wait_tid; // 定义线程 ID
    int value_to_post = 42; // 要 post 的值

    // 初始化信号量,第二个参数 0 表示初始计数为 0
    sem_init(&semaphore, 0, 0);

    // 创建 post 线程,并传递 value_to_post
    if (pthread_create(&post_tid, NULL, post_thread, NULL) != 0) {
        perror("pthread_create");
        return EXIT_FAILURE;
    }

    // 创建 wait 线程
    if (pthread_create(&wait_tid, NULL, wait_thread, NULL) != 0) {
        perror("pthread_create");
        return EXIT_FAILURE;
    }

    // 等待线程结束
    pthread_join(post_tid, NULL);
    pthread_join(wait_tid, NULL);

    // 销毁信号量
    sem_destroy(&semaphore);

    return EXIT_SUCCESS;
}

测试结果符合预期:
在这里插入图片描述

3. 踩坑分析

在工作中遇到过一种情况,等待信号量的进程执行时间过长了,导致下一次post信号的时候,并不是处于wait状态,这样就会导致程序逻辑错误,例如我们把上面代码中的wait进程,sleep时间从1s增加到1.5s,那么就会有下面这种情况,post进程的值和wait进程的值并不能对应上:
在这里插入图片描述
总结:

  1. 在常规的信号量使用中,释放信号量的操作通常应该在至少有一个等待信号量的进程执行等待操作之后进行。这确保了信号量的正确性和互斥性。

  2. 在典型的生产者-消费者问题中,生产者在将数据放入缓冲区之前执行 P 操作以等待空闲缓冲区,然后执行 V 操作以释放空闲缓冲区;消费者在获取数据之前执行 P 操作以等待可用数据,然后执行 V 操作以释放缓冲区。这样的操作顺序保证了生产者和消费者之间的正确同步。

  3. 在一些情况下,可能存在信号量的释放操作先于等待操作的情况,这样的设计通常需要额外的逻辑来确保正确性,比如在资源的生命周期结束时释放信号量,而在等待资源时检查资源是否可用。但在大多数情况下,应遵循释放操作应在等待操作之后执行的原则,以确保程序的正确性和可靠性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

洛谷B3735题解

题目描述 圣诞树共有 n 层&#xff0c;从上向下数第 1 层有 1 个星星、第 2 层有 2 个星星、以此类推&#xff0c;排列成下图所示的形状。 星星和星星之间用绳子连接。第 1,2,⋯,n−1 层的每个星星都向下一层最近的两个星星连一段绳子&#xff0c;最后一层的相邻星星之间连一段…

【开发、测试】接口规范与测试

接口测试基础 url 是互联网标准资源地址&#xff0c;称为统一资源定位符 组成&#xff1a;协议&#xff0c;服务器地址&#xff0c;端口号 HTTP协议 HTTP&#xff1a;超文本传输协议&#xff0c;基于请求与响应的应用层协议 作用&#xff1a;规定了客户端和服务器之间的信…

Spring声明式事务以及事务传播行为

Spring声明式事务以及事务传播行为 Spring声明式事务1.编程式事务2.使用AOP改造编程式事务3.Spring声明式事务 事务传播行为 如果对数据库事务不太熟悉&#xff0c;可以阅读上一篇博客简单回顾一下&#xff1a;MySQL事务以及并发访问隔离级别 Spring声明式事务 事务一般添加到…

前端工程师————CSS学习

选择器分类 选择器分为基础选择器和复合选择器 基础选择器包括&#xff1a;标签选择器&#xff0c;类选择器&#xff0c;id选择器&#xff0c;通配符选择器标签选择器 类选择器 语法&#xff1a;.类名{属性1&#xff1a; 属性值&#xff1b;} 类名可以随便起 多类名使用方式&am…

LeetCode-98. 验证二叉搜索树【树 深度优先搜索 二叉搜索树 二叉树】

LeetCode-98. 验证二叉搜索树【树 深度优先搜索 二叉搜索树 二叉树】 题目描述&#xff1a;解题思路一&#xff1a;中序遍历解题思路二&#xff1a;0解题思路三&#xff1a;0 题目描述&#xff1a; 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树…

【蓝桥杯练习】tarjan算法求解LCA

还是一道比较明显的求LCA(最近公共祖先)模型的题目,我们可以使用多种方法来解决该问题&#xff0c;这里我们使用更好写的离线的tarjan算法来解决该问题。 除去tarjan算法必用的基础数组&#xff0c;我们还有一个数组d[],d[i]记录的是每个点的出度&#xff0c;也就是它的延迟时间…

氮气柜常用的制作材质有哪些?

氮气柜主要用于存储对湿度敏感或需要在低氧环境中保存的精密部件、电子元器件、化学品、文物等&#xff0c;需要确保柜体的密闭性和内部环境的稳定&#xff0c;以防止氧化、受潮或变质。 常见的材质有冷轧钢板&#xff0c;冷轧钢板通过冷轧工艺使钢材组织更紧密&#xff0c;从而…

LeetCode-543. 二叉树的直径【树 深度优先搜索 二叉树】

LeetCode-543. 二叉树的直径【树 深度优先搜索 二叉树】 题目描述&#xff1a;解题思路一&#xff1a;DFS解题思路二&#xff1a;另一种写法DFS解题思路三&#xff1a;0 题目描述&#xff1a; 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任…

不能在主机和虚拟机之间拷贝文本(虚拟机ubuntu16.04)

问题 ubuntu16.04不能在主机和虚拟机之间拷贝文本。 原因 vmware tools没安装好。 解决办法 重新安装vmware tools&#xff0c;步骤入下&#xff1a; 让虚拟机加载C:\Program Files (x86)\VMware\VMware Workstation\linux.iso光盘文件&#xff0c;设置如下&#xff1a; …

C++的并发世界(四)——线程传参

1.全局函数作为传参入口 #include <iostream> #include <thread> #include <string>void ThreadMain(int p1,float p2,std::string str) {std::cout << "p1:" << p1 << std::endl;std::cout << "p2:" <<…

Linux安装conda

目录 conda是什么简介conda与miniconda、anaconda的关系 安装下载文件bash安装激活软件检查安装是否成功配置镜像源 创建环境 conda是什么 简介 conda是一个开源的包管理器和环境管理器&#xff0c;用于安装、运行和更新包和它们的依赖项。它可以轻松地在计算机上创建隔离的环…

刷题DAY44 | 完全背包问题 LeetCode 518-零钱兑换 II 377-组合总和 Ⅳ

完全背包问题模版 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题…

《UE5_C++多人TPS完整教程》学习笔记29 ——《P30 Blaster 角色(Blaster Character)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P30 Blaster 角色&#xff08;Blaster Character&#xff09;》 的学习笔记&#xff0c;该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版&#xff0c;UP主&#xff08;也是译…

网络原理 - HTTP / HTTPS(3)——http响应

目录 一、认识 “状态码”&#xff08;status code&#xff09; 常见的状态码 &#xff08;1&#xff09;200 OK &#xff08;2&#xff09;404 Not Found &#xff08;3&#xff09;403 ForBidden &#xff08;4&#xff09;405 Method Not Allowed &#xff08;5&…

从零到一:基于 K3s 快速搭建本地化 kubeflow AI 机器学习平台

背景 Kubeflow 是一种开源的 Kubernetes 原生框架&#xff0c;可用于开发、管理和运行机器学习工作负载&#xff0c;支持诸如 PyTorch、TensorFlow 等众多优秀的机器学习框架&#xff0c;本文介绍如何在 Mac 上搭建本地化的 kubeflow 机器学习平台。 注意&#xff1a;本文以 …

从头开发一个RISC-V的操作系统(二)RISC-V 指令集架构介绍

文章目录 前提ISA的基本介绍ISA是什么CISC vs RISCISA的宽度 RISC-V指令集RISC-V ISA的命名规范模块化的ISA通用寄存器Hart特权级别内存管理与保护异常和中断 目标&#xff1a;通过这一个系列课程的学习&#xff0c;开发出一个简易的在RISC-V指令集架构上运行的操作系统。 前提…

JavaSE-11笔记【多线程2(+2024新)】

文章目录 6.线程安全6.1 线程安全问题6.2 线程同步机制6.3 关于线程同步的面试题6.3.1 版本16.3.2 版本26.3.3 版本36.3.4 版本4 7.死锁7.1 多线程卖票问题 8.线程通信8.1 wait()和sleep的区别&#xff1f;8.2 两个线程交替输出8.3 三个线程交替输出8.4 线程通信-生产者和消费者…

硬件了解 笔记 2

CPU 内存控制器&#xff1a;负责读写数据 代理系统和平台IO&#xff1a;与主板上的芯片组通信&#xff0c;并管理PC中其他组件之间的数据流 主板&#xff1a;巨大的印刷电路板 Chipset&#xff1a;芯片组&#xff0c;位于散热器下方&#xff0c;直接连接到CPU的系统代理部分 …

应急响应-网站入侵篡改指南Webshell内存马查杀漏洞排查时间分析

知识点 网站入侵篡改防范应对指南 主要需了解&#xff1a;异常特征&#xff0c;处置流程&#xff0c;分析报告等 主要需了解&#xff1a;日志存储&#xff0c;Webshell检测&#xff0c;分析思路等掌握 中间件日志存储&#xff0c;日志格式内容介绍&#xff08;IP,UA头,访问方…

KV260 BOOT.BIN更新 ubuntu22.04 netplan修改IP

KV260 2022.2设置 BOOT.BIN升级 KV260开发板需要先更新BOOT.BIN到2022.2版本&#xff0c;命令如下&#xff1a; sudo xmutil bootfw_update -i “BOOT-k26-starter-kit-202305_2022.2.bin” 注意BOOT.BIN应包含全目录。下面是更新到2022.1 FW的示例&#xff0c;非更新到2022.…