信号量(semaphore):解决并发问题的有力工具

2024-03-24 09:36

本文主要是介绍信号量(semaphore):解决并发问题的有力工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

信号量(semaphore):解决并发问题的有力工具


  记得操作系统课程中,导师曾提出一个引人深思的观点:“信号量几乎可以应对所有的并发问题。”虽然这句话的真实度有待商榷,但不可否认的是,信号量为我们提供了一种更为灵活的方式来处理并发控制难题。

抽象数据类型

  想象一下现实世界中的交通信号灯作为信号量 sem,当 C 车辆欲通过路口执行 P() 操作时,若 sem 小于零,则车辆需等待;只有在 A 车或 B 车完成路口通行并执行 V() 操作后,sem 值加 1,C 车才能接收到信号继续前行。
在这里插入图片描述
  信号量(sem)使用一个整型来表示,有两个原子操作,P操作和V操作:
  P()操作:sem - 1,如果sem < 0则当前线程等待且释放占有资源,否则继续。这里我们可以理解为Java中wait()方法。
  V()操作:sem + 1,如果sem <= 0唤醒一个等待的P,进入临界区。这里我们可以理解为Java中notify()方法。

信号量的实现方式

 信号量主要分为两类:二进制信号量和计数信号量。

  • 二进制信号量,直观来说,利用 0 和 1 表示信号量的两种状态。
  • 计数信号量,具有大于零的初始值,该值只能通过 P、V 操作递减或递增。初始化时设置为大于零的值,意味着可以允许多个执行 P 操作的线程在满足条件的情况下顺利进入相应的临界区。

二进制信号量的运用

  • 二进制信号量实现互斥:通过给定信号量一个初始值,并确保 P、V 操作配对出现,即可实现在多线程间互斥访问资源。
```伪代码// 定义一个mutex信号量且初始值为1// 这里初始值设为1,是要使得首次一定会进入临界区mutex = new Semaphore(1);// 进入临界区前执行p()操作,mutex - 1mutex -> p();···// 临界区代码···// 退出临界区执行v()操作,mutex + 1mutex -> v();
  • 二进制信号量实现同步:在线程 A 必须等待线程 B 达到特定阶段后才可执行时,采用二进制信号量进行同步控制,初始值设为 0,以便 A 线程执行 P 操作时会被挂起等待。
```伪代码
// 创建一个 condition 信号量,初始值为 0
condition = new Semaphore(0);// 线程 A 执行 p() 操作进入等待状态
Thread A:...condition -> p();...// 线程 B 执行 v() 操作唤醒线程 A
Thread B:...condition -> v();...

计数信号量的使用

  面对更为复杂的并发场景,计数信号量展现出了其独特的优势。

计数信号量实现互斥与同步

  考虑经典的生产者-消费者问题,假设存在一个共享缓冲区,生产者向其中填充数据,消费者从中取出数据,系统需满足以下条件:

  1. 同一时刻只有一个线程能操作缓冲区(互斥);
  2. 缓冲区为空时,消费者需等待生产者(同步);
  3. 缓冲区满时,生产者需等待消费者(同步)。

为此,我们定义三个信号量来保证这些约束得以满足:

  • 二进制信号量,负责实现线程间的互斥;
  • 计数信号量 emptyBuffer,记录缓冲区为空的情况;
  • 计数信号量 fullBuffer,记录缓冲区满载的情况。
```伪代码
class Buffer{// 定义二进制信号量用于缓冲区buffer互斥mutex = new Semaphore(1);// 定义计数信号量fullBuffer// 初始值为表示缓冲区buffer一开始是空的fullBuffer = new Semaphore(0);// 定义计数信号量emptyBuffer// 初始值为缓冲区buffer的最大值,emptyBuffer = new Semaphore(n);
}// 生产者生产操作
deposit(c){// 计数信号量emptyBuffer,检查是否已满载(0表示满载)// 计数信号量emptyBuffer执行p操作,-1// 初始值为n且大于0,表示可重复进入n次或者n个生产者emptyBuffer -> p();// 二进制信号量mutex执行p操作,检查是否可进入临界区mutex -> p();···add c to buffer···// 二进制信号量mutex执行v操作mutex -> v();// 计数信号量fullBuffer,告诉消费者取数据// 计数信号量fullBuffer执行v操作,+1fullBuffer -> v();
}// 消费者消费操作
remove(c){// 计数信号量fullBuffer,检查是否为空,-1// 因为fullBuffer的初始值为0,则一开始消费者无法进入缓冲区buffer进行取数据fullBuffer -> p();// 二进制信号量mutex执行p操作,检查是否可进入临界区mutex -> p();···add c to buffer···// 二进制信号量mutex执行v操作mutex -> v();// 计数信号量emptyBuffer,告诉生产者生产,+1emptyBuffer -> v();
}

关于 P、V 操作顺序的探讨

  文中指出,V 操作仅涉及信号量加 1 并唤醒一个线程,所以这里的 V 操作顺序是可以任意交换而不影响程序逻辑的。然而,P 操作的执行顺序则至关重要。例如,在生产者逻辑中,不能随意交换 emptyBuffer -> p()mutex -> p() 的顺序。若交换后,当缓冲区已满时,新来的生产者可能会先获取到互斥锁,然后尝试减少 emptyBuffer 信号量并陷入等待,从而导致持有互斥锁不放,其他线程无法获得锁,进而产生死锁现象。

  综上所述,本文详尽地解释了信号量的基本概念和如何在不同的并发场景中应用二进制信号量与计数信号量实现互斥和同步控制,并通过实例剖析了操作顺序对程序正确性的影响,是一篇对信号量原理及实践有着深入解析的文章。通过这样的整理和讲解,希望能帮助读者更好地理解和掌握信号量在解决并发问题方面的强大能力。


一键三连,让我的信心像气球一样膨胀!

这篇关于信号量(semaphore):解决并发问题的有力工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/841195

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

504 Gateway Timeout网关超时的根源及完美解决方法

《504GatewayTimeout网关超时的根源及完美解决方法》在日常开发和运维过程中,504GatewayTimeout错误是常见的网络问题之一,尤其是在使用反向代理(如Nginx)或... 目录引言为什么会出现 504 错误?1. 探索 504 Gateway Timeout 错误的根源 1.1 后端

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

深度剖析SpringBoot日志性能提升的原因与解决

《深度剖析SpringBoot日志性能提升的原因与解决》日志记录本该是辅助工具,却为何成了性能瓶颈,SpringBoot如何用代码彻底破解日志导致的高延迟问题,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言第一章:日志性能陷阱的底层原理1.1 日志级别的“双刃剑”效应1.2 同步日志的“吞吐量杀手”

MySQL 表空却 ibd 文件过大的问题及解决方法

《MySQL表空却ibd文件过大的问题及解决方法》本文给大家介绍MySQL表空却ibd文件过大的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录一、问题背景:表空却 “吃满” 磁盘的怪事二、问题复现:一步步编程还原异常场景1. 准备测试源表与数据

解决Nginx启动报错Job for nginx.service failed because the control process exited with error code问题

《解决Nginx启动报错Jobfornginx.servicefailedbecausethecontrolprocessexitedwitherrorcode问题》Nginx启... 目录一、报错如下二、解决原因三、解决方式总结一、报错如下Job for nginx.service failed bec