进程同步之信号量机制

信号量机制

信号量机制是一种用于进程同步和互斥的基本工具,特别是在并发编程中,广泛用于控制对共享资源的访问,避免数据竞争和死锁等问题。信号量机制由荷兰计算机科学家Edsger Dijkstra在1965年提出,并在操作系统的进程同步中发挥了重要作用。经历了整型信号量、 记录型信号量、AND型信号量和信号量集。

1)整型信号量

Dijkstra最初提出的信号量为表示临界资源的一个整型量S。S>0时表示有S个资源可用;S<=0表示该资源已被分配完。另外,定义了两个原语wait(S)和signal(S)用于资源的分配和释放,这两个原语的C语言伪代码如下:

 wait(int &S){while (S<=0);S=S-1;}​signal(int &S){S=S+1;}

wait原语(也叫作P操作)首先通过while循环测试是否S<=0,如果是则继续等待,否则将S的值减1,资源分配成功,可以进入临界区访问。 signal原语(也叫做V操作)只有一条语句,即将S值加1,表示释放1个资源。

示例:使用整型信号量进行互斥控制

// 信号量 S,初始化为1,表示有1个资源
int S = 1;// wait原语(P操作)
void wait(int *S) {while (*S <= 0);  *S = *S - 1; 
}// signal原语(V操作)
void signal(int *S) {*S = *S + 1; 
}// 临界区函数
void *p1(void *p) {wait(&S); printf("线程1进入临界区\n");signal(&S);  return NULL;
}void *p2(void *p) {wait(&S); printf("线程2进入临界区\n");signal(&S); return NULL;
}

解释:

信号量 S:它是一个整型变量,表示可用的资源数量,初始化为1,表示有一个资源可以分配。

wait 操作(P操作): wait操作会检查信号量S的值。如果 S小于等于0,表示资源不可用,当前线程将进入等待状态。如果 S 大于0,表示有资源可用,信号量 S会减1,表示资源已被分配给当前线程,线程可以访问共享资源。

signal操作(V操作): signal操作会释放一个资源,信号量 S增加1。如果有等待的线程,它们会根据信号量的值重新获得资源。

线程 p1和 p2: 这两个线程都访问共享资源,每个线程在进入临界区前都调用 wait(S)请求资源,执行完任务后调用 signal(S) 释放资源。 由于信号量的控制,线程 p1和 p2 会互斥地访问共享资源。

2.)记录型信号量

为了解决整型信号量中的“忙等”问题,即当没有资源可用时,进程不断等待而不释放CPU,可以采用记录型信号量(semaphore)。在这种信号量机制中,我们引入了阻塞进程队列来管理等待资源的进程。记录型信号量由一个结构体组成,包含两个成员:整型变量value和进程阻塞队列Lvalue表示当前可用的资源数量,当value > 0时,表示有可用资源;当value < 0时,value的绝对值表示正在等待资源的进程数量。

此外,L是一个进程队列,包含那些因无法获取资源而被阻塞的进程。当资源可用时,这些被阻塞的进程可以被唤醒,继续执行。因此,记录型信号量通过引入阻塞队列来避免了“忙等”,实现了进程的高效调度。

伪代码如下:

typedef struct{int value;struct process_control_block *L
}semaphore;
//value>O时,value为资源可用数目
//value<O,|value|为已阻塞进程的数目
//L为阻塞进程队列首指针wait(int &S){S.value = S.value -1;if (S.value<0) block(S.L);
}
//阻塞到队尾,
//程序计数器定位在Wait之后signal(int &S){S.value = S.value+1;if(S.value<=0) wake(S.L);//唤醒队首
}

示例:

#include <stdio.h>
#include <pthread.h>typedef struct process_control_block {pthread_t thread;  // 阻塞进程的线程IDstruct process_control_block *next;  // 指向下一个进程
} PCB;typedef struct {int value;  // 信号量的值,表示资源的数量PCB *L;     // 阻塞进程队列的头指针
} semaphore;semaphore S = {1, NULL};  // 初始化信号量,资源数量为1// 模拟进程被阻塞
void block(PCB *L) {PCB *new_pcb = (PCB *)malloc(sizeof(PCB));new_pcb->thread = pthread_self();  // 获取当前进程的线程IDnew_pcb->next = NULL;// 将新进程加入到阻塞队列的尾部if (L == NULL) {L = new_pcb;} else {PCB *temp = L;while (temp->next != NULL) {temp = temp->next;}temp->next = new_pcb;}// 阻塞进程的代码逻辑printf("进程 %lu 被阻塞。\n", pthread_self());pthread_exit(NULL);  // 当前线程挂起
}// 模拟进程被唤醒
void wake(PCB *L) {if (L != NULL) {PCB *temp = L;L = L->next;  // 唤醒队列中的第一个进程printf("进程 %lu 被唤醒。\n", temp->thread);free(temp);  // 释放唤醒的进程}
}// wait原语
void wait(semaphore *S) {S->value = S->value - 1;  // 请求资源,信号量值减1if (S->value < 0) {block(S->L);  // 资源不足,进程被阻塞}
}// signal原语
void signal(semaphore *S) {S->value = S->value + 1;  // 释放资源,信号量值加1if (S->value <= 0) {wake(S->L);  // 唤醒阻塞队列中的一个进程}
}// 线程函数
void *process(void *param) {printf("进程 %lu 正在尝试进入临界区。\n", pthread_self());wait(&S);  // 请求资源printf("进程 %lu 已进入临界区。\n", pthread_self());signal(&S);  // 释放资源return NULL;
}int main() {pthread_t t1, t2;// 创建两个线程pthread_create(&t1, NULL, process, NULL);pthread_create(&t2, NULL, process, NULL);// 等待线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);return 0;
}
3)AND型信号量

记录型信号量一次只能申请一种资源,而当一个进程需要同时获取多种临界资源时,若资源申请顺序不当,很容易导致死锁的发生。为了解决这个问题,引入了AND信号量,它允许一次申请多种资源,每种资源申请一个单位。只有当所有申请的资源都满足要求时,才会全部分配,否则一种资源也不会分配。

AND型信号量通过SwaitSsignal两个原语进行资源的申请和释放。Swait的参数为涉及的n种资源的信号量,分别定义为S_1S_n。在Swait操作中,首先检查n种资源的可用数量(即信号量的value)是否都大于或等于1。如果满足条件,则将所有信号量的value值减1,表示资源分配成功;如果不满足条件,则从S_1S_n中查找第一个value值小于1的信号量S_i,并将当前进程阻塞到S_i的阻塞队列S_i.L中。此时,程序的计数器将重新定位到Swait操作的起点,等待资源满足条件后继续执行。

伪代码如下:

// Swait原语:请求多个资源
void Swait(semaphore S_1, semaphore S_2, ..., semaphore S_n) {// 判断所有信号量的value是否都大于等于1if (S_1.value >= 1 && S_2.value >= 1 && ... && S_n.value >= 1) {// 如果所有资源都可用,则将每个资源的value值减1for (int i = 1; i <= n; i++) {S_i.value = S_i.value - 1;}} else {// 否则,找到第一个不可用的资源for (int i = 1; i <= n && S_i.value >= 1; i++);// 将进程阻塞到第一个不可用资源的阻塞队列中block(S_i.L);// 程序计数器重新定位到Swait操作的起点,等待资源可用}
}// Ssignal原语:释放多个资源
void Ssignal(semaphore S_1, semaphore S_2, ..., semaphore S_n) {// 释放每个资源并将value加1for (int i = 1; i <= n; i++) {S_i.value = S_i.value + 1;// 如果该资源的value小于等于0,表示有阻塞的进程,需要唤醒if (S_i.value <= 0) {wake(S_i.L);}}
}

示例:

#include <stdio.h>
#include <pthread.h>typedef struct process_control_block {pthread_t thread;  // 阻塞进程的线程IDstruct process_control_block *next;  // 指向下一个进程
} PCB;typedef struct {int value;  // 信号量的值,表示资源的数量PCB *L;     // 阻塞进程队列的头指针
} semaphore;semaphore S1 = {1, NULL};  // 资源1,初始值为1
semaphore S2 = {1, NULL};  // 资源2,初始值为1
semaphore S3 = {1, NULL};  // 资源3,初始值为1// 模拟进程被阻塞
void block(PCB *L) {PCB *new_pcb = (PCB *)malloc(sizeof(PCB));new_pcb->thread = pthread_self();  // 获取当前进程的线程IDnew_pcb->next = NULL;// 将新进程加入到阻塞队列的尾部if (L == NULL) {L = new_pcb;} else {PCB *temp = L;while (temp->next != NULL) {temp = temp->next;}temp->next = new_pcb;}// 阻塞进程的代码逻辑printf("进程 %lu 被阻塞。\n", pthread_self());pthread_exit(NULL);  // 当前线程挂起
}// 模拟进程被唤醒
void wake(PCB *L) {if (L != NULL) {PCB *temp = L;L = L->next;  // 唤醒队列中的第一个进程printf("进程 %lu 被唤醒。\n", temp->thread);free(temp);  // 释放唤醒的进程}
}// Swait原语:请求多个资源
void Swait(semaphore *S_1, semaphore *S_2, semaphore *S_3) {if (S_1->value >= 1 && S_2->value >= 1 && S_3->value >= 1) {// 如果所有资源都可用,则将资源的value值减1S_1->value--;S_2->value--;S_3->value--;printf("资源已分配给进程 %lu。\n", pthread_self());} else {// 否则,找到第一个不可用的资源if (S_1->value < 1) {block(S_1->L);  // 阻塞进程} else if (S_2->value < 1) {block(S_2->L);} else if (S_3->value < 1) {block(S_3->L);}}
}// Ssignal原语:释放多个资源
void Ssignal(semaphore *S_1, semaphore *S_2, semaphore *S_3) {S_1->value++;S_2->value++;S_3->value++;printf("资源已被进程 %lu 释放。\n", pthread_self());// 唤醒被阻塞的进程wake(S_1->L);wake(S_2->L);wake(S_3->L);
}// 线程函数
void *process(void *param) {printf("进程 %lu 正在尝试请求资源。\n", pthread_self());Swait(&S1, &S2, &S3);  // 请求资源printf("进程 %lu 已进入临界区。\n", pthread_self());Ssignal(&S1, &S2, &S3);  // 释放资源return NULL;
}int main() {pthread_t t1, t2, t3;// 创建三个线程pthread_create(&t1, NULL, process, NULL);pthread_create(&t2, NULL, process, NULL);pthread_create(&t3, NULL, process, NULL);// 等待线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);return 0;
}
4)信号量集

当一个进程需要申请多种资源,每种资源需要多个单位,并且当资源的可用数量低于设定的下限时,不应进行资源分配。为便于软件开发,AND型信号量机制进行了扩展,定义了信号量集。

信号量集的资源申请和释放操作与AND型信号量相似,但参数的构成有所不同。具体来说,Swait操作的参数包括n种资源信号量S_i、每种资源的申请数量d_i以及资源分配的下限t_i的序列。在Swait中,首先判断每种资源的可用数量(即信号量的value)是否大于或等于对应的下限t_i,如果满足条件,则将每种资源的信号量value减去相应的d_i,表示分配成功;如果不满足条件,则检查所有资源的可用性,直到发现第一个不满足条件的信号量S_i,然后将当前进程阻塞到该信号量S_i的阻塞队列S_i.L中。此时,程序的计数器将重新定位到Swait操作的起点,等待资源满足条件后继续执行。

伪代码如下:

// Swait原语:请求多个资源,指定每种资源的分配下限和申请数量
void Swait(semaphore S_1, int t_1, int d_1, ..., semaphore S_n, int t_n, int d_n) {// 判断所有信号量的value是否都大于等于对应的分配下限if (S_1.value >= t_1 && S_2.value >= t_2 && ... && S_n.value >= t_n) {// 如果所有资源都满足分配条件,则将资源的value值减去对应的申请数量for (int i = 1; i <= n; i++) {S_i.value = S_i.value - d_i;}} else {// 否则,找到第一个不满足资源要求的信号量for (int i = 1; i <= n && S_i.value >= t_i; i++);// 将进程阻塞到不满足条件的信号量的阻塞队列中block(S_i.L);// 程序计数器重新定位到Swait操作的起点,等待资源可用}
}// Ssignal原语:释放多个资源,指定每种资源的释放数量
void Ssignal(semaphore S_1, int d_1, ..., semaphore S_n, int d_n) {// 释放每个资源并将value加上对应的释放数量for (int i = 1; i <= n; i++) {S_i.value = S_i.value + d_i;// 唤醒阻塞队列中的进程if (S_i.value <= 0) {wake(S_i.L);}}
}

示例:

#include <stdio.h>
#include <pthread.h>typedef struct process_control_block {pthread_t thread;  // 阻塞进程的线程IDstruct process_control_block *next;  // 指向下一个进程
} PCB;typedef struct {int value;  // 信号量的值,表示资源的数量PCB *L;     // 阻塞进程队列的头指针
} semaphore;semaphore S1 = {5, NULL};  // 资源1,初始值为5
semaphore S2 = {5, NULL};  // 资源2,初始值为5
semaphore S3 = {5, NULL};  // 资源3,初始值为5// 模拟进程被阻塞
void block(PCB *L) {PCB *new_pcb = (PCB *)malloc(sizeof(PCB));new_pcb->thread = pthread_self();  // 获取当前进程的线程IDnew_pcb->next = NULL;// 将新进程加入到阻塞队列的尾部if (L == NULL) {L = new_pcb;} else {PCB *temp = L;while (temp->next != NULL) {temp = temp->next;}temp->next = new_pcb;}// 阻塞进程的代码逻辑printf("进程 %lu 被阻塞。\n", pthread_self());pthread_exit(NULL);  // 当前线程挂起
}// 模拟进程被唤醒
void wake(PCB *L) {if (L != NULL) {PCB *temp = L;L = L->next;  // 唤醒队列中的第一个进程printf("进程 %lu 被唤醒。\n", temp->thread);free(temp);  // 释放唤醒的进程}
}// Swait原语:请求多个资源,指定每种资源的分配下限和申请数量
void Swait(semaphore *S_1, int t_1, int d_1, semaphore *S_2, int t_2, int d_2, semaphore *S_3, int t_3, int d_3) {// 判断所有信号量的value是否都大于等于对应的分配下限if (S_1->value >= t_1 && S_2->value >= t_2 && S_3->value >= t_3) {// 如果所有资源都满足分配条件,则将资源的value值减去对应的申请数量S_1->value -= d_1;S_2->value -= d_2;S_3->value -= d_3;printf("资源已分配给进程 %lu。\n", pthread_self());} else {// 否则,找到第一个不满足资源要求的信号量if (S_1->value < t_1) {block(S_1->L);  // 阻塞进程} else if (S_2->value < t_2) {block(S_2->L);} else if (S_3->value < t_3) {block(S_3->L);}}
}// Ssignal原语:释放多个资源,指定每种资源的释放数量
void Ssignal(semaphore *S_1, int d_1, semaphore *S_2, int d_2, semaphore *S_3, int d_3) {S_1->value += d_1;S_2->value += d_2;S_3->value += d_3;printf("资源已被进程 %lu 释放。\n", pthread_self());// 唤醒阻塞队列中的进程wake(S_1->L);wake(S_2->L);wake(S_3->L);
}// 线程函数
void *process(void *param) {printf("进程 %lu 正在尝试请求资源。\n", pthread_self());Swait(&S1, 2, 1, &S2, 2, 1, &S3, 2, 1);  // 请求资源printf("进程 %lu 已进入临界区。\n", pthread_self());Ssignal(&S1, 1, &S2, 1, &S3, 1);  // 释放资源return NULL;
}int main() {pthread_t t1, t2, t3;// 创建三个线程pthread_create(&t1, NULL, process, NULL);pthread_create(&t2, NULL, process, NULL);pthread_create(&t3, NULL, process, NULL);// 等待线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);return 0;
}

信号量机制之苹果-橘子问题-CSDN博客

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

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

相关文章

SOME/IP协议详解 基础解读 涵盖SOME/IP协议解析 SOME/IP通讯机制 协议特点 错误处理机制

车载以太网协议栈总共可划分为五层&#xff0c;分别为物理层&#xff0c;数据链路层&#xff0c;网络层&#xff0c;传输层&#xff0c;应用层&#xff0c;其中今天所要介绍的内容SOME/IP就是一种应用层协议。 SOME/IP协议内容按照AUTOSAR中的描述&#xff0c;我们可以更进一步…

springboot vue uniapp 仿小红书 1:1 还原 (含源码演示)

线上预览: 移动端 http://8.146.211.120:8081/ 管理端 http://8.146.211.120:8088/ 小红书凭借优秀的产品体验 和超高人气 目前成为笔记类产品佼佼者 此项目将详细介绍如何使用Vue.js和Spring Boot 集合uniapp 开发一个仿小红书应用&#xff0c;凭借uniapp 可以在h5 小程序 app…

Win11右键菜单实现

主要参考Win11 Context Menu Demo 此工程是vs2022编译&#xff0c;vs2019先修改下 base.h 方可编译过 编译好dll以后 拷贝至SparsePackage目录下 生成稀疏包msix 就拿他工程里面的改&#xff0c;编辑AppxManifest.xml&#xff0c;配置都要对&#xff0c;一个不对可能都失败&a…

像JSONDecodeError: Extra data: line 2 column 1 (char 134)这样的问题怎么解决

问题介绍 今天处理返回的 JSON 的时候&#xff0c;出现了下面这样的问题&#xff1a; 处理这种问题的时候&#xff0c;首先你要看一下当前的字符串格式是啥样的&#xff0c;比如我查看后发现是下面这样的&#xff1a; 会发现这个字符串中间没有逗号&#xff0c;也就是此时的J…

what?ngify 比 axios 更好用,更强大?

文章目录 前言一、什么是ngify&#xff1f;二、npm安装三、发起请求3.1 获取 JSON 数据3.2 获取其他类型的数据3.3 改变服务器状态3.4 设置 URL 参数3.5 设置请求标头3.6 与服务器响应事件交互3.7 接收原始进度事件3.8 处理请求失败3.9 Http Observables 四、更换 HTTP 请求实现…

Linux Kernel 之十 详解 PREEMPT_RT、Xenomai 的架构、源码、构建及使用

概述 现在的 RTOS 基本可以分为 Linux 阵营和非 Linux 阵营这两大阵营。非 Linux 阵营的各大 RTOS 都是独立发展,使用上也相对独立;而 Linux 阵营则有多种不同的实现方法来改造 Linux 以实现实时性要求。本文我们重点关注 Linux 阵营的实时内核实现方法! 本文我们重点关注 …

【拒绝算法PUA】3065. 超过阈值的最少操作数 I

系列文章目录 【拒绝算法PUA】0x00-位运算 【拒绝算法PUA】0x01- 区间比较技巧 【拒绝算法PUA】0x02- 区间合并技巧 【拒绝算法PUA】0x03 - LeetCode 排序类型刷题 【拒绝算法PUA】LeetCode每日一题系列刷题汇总-2025年持续刷新中 C刷题技巧总结&#xff1a; [温习C/C]0x04 刷…

ClickHouse-CPU、内存参数设置

常见配置 1. CPU资源 1、clickhouse服务端的配置在config.xml文件中 config.xml文件是服务端的配置&#xff0c;在config.xml文件中指向users.xml文件&#xff0c;相关的配置信息实际是在users.xml文件中的。大部分的配置信息在users.xml文件中&#xff0c;如果在users.xml文…

《自动驾驶与机器人中的SLAM技术》ch9:自动驾驶车辆的离线地图构建

目录 1 点云建图的流程 2 前端实现 2.1 前端流程 2.2 前端结果 3 后端位姿图优化与异常值剔除 3.1 两阶段优化流程 3.2 优化结果 ① 第一阶段优化结果 ② 第二阶段优化结果 4 回环检测 4.1 回环检测流程 ① 遍历第一阶段优化轨迹中的关键帧。 ② 并发计算候选回环对…

GPT 系列论文精读:从 GPT-1 到 GPT-4

学习 & 参考资料 前置文章 Transformer 论文精读 机器学习 —— 李宏毅老师的 B 站搬运视频 自监督式学习(四) - GPT的野望[DLHLP 2020] 來自猎人暗黑大陆的模型 GPT-3 论文逐段精读 —— 沐神的论文精读合集 GPT&#xff0c;GPT-2&#xff0c;GPT-3 论文精读【论文精读】…

大数据技术Kafka详解 ⑤ | Kafka中的CAP机制

目录 1、分布式系统当中的CAP理论 1.1、CAP理论 1.2、Partitiontolerance 1.3、Consistency 1.4、Availability 2、Kafka中的CAP机制 C软件异常排查从入门到精通系列教程&#xff08;核心精品专栏&#xff0c;订阅量已达600多个&#xff0c;欢迎订阅&#xff0c;持续更新…

riscv架构下linux4.15实现early打印

在高版本linux6.12.7源码中&#xff0c;early console介绍&#xff0c;可参考《riscv架构下linux6.12.7实现early打印》文章。 1 什么是early打印 适配内核到新的平台&#xff0c;基本环境搭建好之后&#xff0c;首要的就是要调通串口&#xff0c;方便后面的信息打印。 正常流…

improve-gantt-elastic(vue2中甘特图实现与引入)

1.前言 项目开发中需要使用甘特图展示项目实施进度&#xff0c;左侧为表格计划&#xff0c;右侧为图表进度展示。wl-gantt-mater&#xff0c;dhtmlx尝试使用过可拓展性受到限制。gantt-elastic相对简单&#xff0c;可操作性强&#xff0c;基础版本免费。 甘特图&#xff08;Gan…

力扣 全排列

回溯经典例题。 题目 通过回溯生成所有可能的排列。每次递归时&#xff0c;选择一个数字&#xff0c;直到选满所有数字&#xff0c;然后记录当前排列&#xff0c;回到上层时移除最后选的数字并继续选择其他未选的数字。每次递归时&#xff0c;在 path 中添加一个新的数字&…

1/13+2

运算符重载 myString.h #ifndef MYSTRING_H #define MYSTRING_H #include <cstring> #include <iostream> using namespace std; class myString {private:char *str; //记录c风格的字符串int size; //记录字符串的实际长度int capacity; …

【HM-React】08. Layout模块

基本结构和样式reset 结构创建 实现步骤 打开 antd/Layout 布局组件文档&#xff0c;找到示例&#xff1a;顶部-侧边布局-通栏拷贝示例代码到我们的 Layout 页面中分析并调整页面布局 代码实现 pages/Layout/index.js import { Layout, Menu, Popconfirm } from antd impor…

计算机视觉算法实战——实时车辆检测和分类(主页有相关源码)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ ​​​​​​​​​​​​​​​​​​ 1. 领域介绍✨✨ 实时车辆检测和分类是计算机视觉中的一个重要应用领域&#xff0c;旨在从视频流或…

使用 selenium-webdriver 开发 Web 自动 UI 测试程序

优缺点 优点 有时候有可能一个改动导致其他的地方的功能失去效果&#xff0c;这样使用 Web 自动 UI 测试程序可以快速的检查并定位问题&#xff0c;节省大量的人工验证时间 缺点 增加了维护成本&#xff0c;如果功能更新过快或者技术更新过快&#xff0c;维护成本也会随之提高…

性能测试工具Jmeter分布式运行

性能测试工具JMeter的分布式执行是一种用于增强压力测试能力的技术方案&#xff0c;它允许用户通过多台机器来共同完成同一个测试计划的执行。这种方式特别适用于需要模拟成百上千甚至上万用户并发访问的情况&#xff0c;当单台机器由于硬件资源&#xff08;如CPU、内存、网络I…

弥散张量分析开源软件 DSI Studio 简体中文汉化版可以下载了

网址&#xff1a; (63条消息) DSIStudio简体中文汉化版(2022年7月)-算法与数据结构文档类资源-CSDN文库