同步与互斥之信号量
目录
1、信号量用于线程的互斥
验证
2、信号量用于线程的同步
验证
3、无名信号量用于进程间互斥
代码一
代码二
验证
4、有名信号量 用于进程间同步和互斥
验证
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被
用来控制对公共资源的访问。当信号量值大于 0 时,则可以访问,否则将阻塞。信号量是一种用于控制进程或线程同步和互斥的机制。它通常由一个计数器和一组等待的进程或线程组成。当进程或线程需要访问共享资源时,它会尝试获取一个信号量。如果信号量的计数器大于0,则进程或线程可以获得信号量并继续执行。否则,进程或线程将被阻塞,直到有信号量可用。
PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。
信号量数据类型为:sem_t
1、信号量用于线程的互斥
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>// 定义一个信号量(用于互斥)
sem_t sem;void my_printf(char *str)
{int i = 0;while (str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}
}
void *fun1(void *arg)
{// P 操作sem_wait(&sem);my_printf((char *)arg);// V 操作sem_post(&sem);
}
void *fun2(void *arg)
{// P 操作sem_wait(&sem);my_printf((char *)arg);// V 操作sem_post(&sem);
}
void *fun3(void *arg)
{// P 操作sem_wait(&sem);my_printf((char *)arg);// V 操作sem_post(&sem);
}
int main(int argc, char *argv[])
{// 信号量初始化为1 第二个参数0表示用于线程,第三个信号量初始值sem_init(&sem, 0, 1);pthread_t tid1, tid2, tid3;pthread_create(&tid1, NULL, fun1, "this is tid1\n");pthread_create(&tid2, NULL, fun2, "this is tid2\n");pthread_create(&tid3, NULL, fun3, "this is tid3\n");pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);//销毁信号量sem_destroy(&sem);return 0;
}
实现了三个线程的并发输出,但是通过信号量sem保证了只有一个线程在输出时访问标准输出流,即实现了互斥。
定义了一个信号量 sem 用于实现互斥访问。三个线程 tid1、tid2 和 tid3 将同时运行,它们的目标是调用 my_printf 函数打印不同的字符串,但是这些线程之间需要互斥访问共享资源,否则会导致输出的字符串混乱。
在 fun1、fun2 和 fun3 函数中,首先调用 sem_wait 函数来申请信号量资源,表示进入临界区,如果信号量的值为1,则将其减1,表示申请成功,否则阻塞等待。然后调用 my_printf 函数输出相应的字符串,最后调用 sem_post 函数释放信号量资源,将其加1,表示退出临界区,其他线程就可以申请这个资源了。
主函数中,初始化了信号量,创建三个线程,分别调用 fun1、fun2 和 fun3 函数,最后等待三个线程执行结束,销毁信号量。
验证

线程的执行顺序是不确定的,由操作系统决定。虽然代码中是先创建tid1线程,但是操作系统可能会优先执行tid3线程,所以最终的执行结果可能是tid3先执行,然后是tid2,最后是tid1。因此,不能依赖代码中的线程创建顺序来确定线程的执行顺序。
2、信号量用于线程的同步
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>// 定义三个信号量(用于同步)
sem_t sem1, sem2, sem3;
void my_printf(char *str)
{int i = 0;while (str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}
}
void *syn_fun1(void *arg)
{ // psem_wait(&sem1);my_printf((char *)arg);// vsem_post(&sem2);
}
void *syn_fun2(void *arg)
{ // psem_wait(&sem2);my_printf((char *)arg);// vsem_post(&sem3);
}
void *syn_fun3(void *arg)
{ // psem_wait(&sem3);my_printf((char *)arg);// vsem_post(&sem1);
}
int main(int argc, char *argv[])
{// 信号量初始化为1 第二个参数0表示用于线程sem_init(&sem1, 0, 1);sem_init(&sem2, 0, 0);sem_init(&sem3, 0, 0);pthread_t tid1, tid2, tid3;pthread_create(&tid1, NULL, syn_fun1, "this is tid1\n");pthread_create(&tid2, NULL, syn_fun2, "this is tid2\n");pthread_create(&tid3, NULL, syn_fun3, "this is tid3\n");pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);// 销毁信号量sem_destroy(&sem1);sem_destroy(&sem2);sem_destroy(&sem3);return 0;
}
实现了三个线程的同步执行,即按照指定的顺序依次输出三个字符串。其中,使用了三个信号量来实现同步,分别为sem1、sem2、sem3。这三个信号量的初始值分别为1、0、0,表示sem1可以被访问,而sem2和sem3需要等待其他线程的信号才能被访问。三个线程分别对应syn_fun1、syn_fun2、syn_fun3函数,每个函数中都使用了sem_wait和sem_post来对信号量进行操作。其中,sem_wait用于P操作,即尝试获取信号量,如果信号量的值为0,则线程会阻塞等待其他线程的信号;而sem_post用于V操作,即释放信号量,将信号量的值加1,表示其他线程可以访问这个信号量了。最后,在main函数中创建三个线程,并使用pthread_join等待线程结束,最后销毁信号量。
验证

虽然定义了三个信号量,但是初始值为1的信号量只有一个,所以,只有为1的信号量对应的sem1能运行,其他的需要等待信号,也就确保了只有tid1先运行,不会像上面互斥一样随机运行
3、无名信号量用于进程间互斥
无名信号量是一种特殊类型的信号量,它只能被同一进程内的线程使用。它们不需要被命名,因此被称为“无名信号量”。无名信号量通常用于控制线程之间的同步和互斥。在代码示例中,sem_init 函数用于初始化一个无名信号量,而 sem_wait 和 sem_post 函数分别用于等待和释放信号量。
代码一
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/mman.h>void my_printf(char *str)
{int i = 0;while (str[i] != '\0')printf("%c", str[i++]);fflush(stdout);sleep(1);
}
int main(int argc, char *argv[])
{// 定义一个无名信号量// MAP_ANONYMOUS匿名映射 ‐1不需要文件描述符sem_t *sem = mmap(NULL,sizeof(sem_t),PROT_READ |PROT_WRITE,MAP_SHARED |MAP_ANONYMOUS,-1, 0);// 无名信号量的初始化 第一个1表示进程 第二个1表示初始化值1sem_init(sem, 1, 1);pid_t pid = fork();if (pid == 0) // 子进程{// psem_wait(sem);my_printf("child process\n");// vsem_post(sem);}else if (pid > 0) // 父进程{// psem_wait(sem);my_printf("father process\n");// vsem_post(sem);}sem_destroy(sem);return 0;
}
通过调用mmap函数,将信号量映射到进程的虚拟内存空间中。然后,使用sem_init初始化信号量的值为1,表示当前没有其他进程在访问它。接着,通过调用fork函数创建一个子进程。在父进程中,调用sem_wait函数尝试获得对信号量的访问权。由于这是第一个进程,因此它能够获得对信号量的访问权。它打印一条消息,然后使用sem_post函数释放对信号量的访问权。在子进程中,它也会尝试获得对信号量的访问权,但是由于父进程已经获得了对信号量的访问权,所以子进程必须等待父进程释放对信号量的访问权。然后它打印一条消息,再次使用sem_post函数释放对信号量的访问权。最后,调用sem_destroy函数销毁信号量并释放资源。
mmap是一个系统调用,用于将一个文件或设备映射到内存中。在上面的代码中,它的作用是为共享内存分配一块内存区域,返回的是指向这个区域的指针。mmap函数的调用参数解释如下:
- NULL:表示分配内存区域的起始地址,由系统自动分配
- sizeof(sem_t):表示需要分配的内存区域的大小
- PROT_READ | PROT_WRITE:表示内存区域的访问权限,这里是可读可写
- MAP_SHARED:表示这块内存区域是被多个进程共享的,使用了
MAP_ANONYMOUS标志来创建一个匿名映射,一个无名的共享内存区域,不需要与文件关联 - fd:表示文件描述符,这里是共享内存的文件描述符,‐1不需要文件描述符
- 0:表示偏移量,这里没有偏移,从文件开头开始映射
- 成功 返回映射区的首地址
代码二
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/mman.h>void my_printf(char *str)
{int i=0;while(str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}
}
int main(int argc, char *argv[])
{//定义一个无名信号量//MAP_ANONYMOUS匿名映射 ‐1不需要文件描述符sem_t *sem1 = mmap(NULL, sizeof(sem_t),PROT_READ|PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);sem_t *sem2 = mmap(NULL, sizeof(sem_t),PROT_READ|PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);//无名信号量的初始化 第一个1表示进程 第二个1表示初始化值1sem_init(sem1,1,1);sem_init(sem2,1,0);pid_t pid =fork();if(pid ==0)//子进程{//psem_wait(sem1);my_printf("this is child process\n");//vsem_post(sem2);}else if(pid >0){//psem_wait(sem2);my_printf("this is father process\n");//vsem_post(sem1);}//销毁信号量sem_destroy(sem1);sem_destroy(sem2);return 0;}
这段代码定义了两个无名信号量sem1和sem2,并使用mmap将它们分别映射到了进程的虚拟内存空间中。然后使用这两个无名信号量实现了进程间的同步。不同于前面那段代码,这段代码使用了两个不同的指针变量sem1和sem2分别指向映射到内存中的两个无名信号量。同时在创建子进程时,子进程通过sem2来等待父进程打开它,父进程通过sem1来等待子进程打开它。这样就实现了进程间的同步。总的来说,这两段代码使用的都是无名信号量来实现进程间同步,只不过一个使用了一个无名信号量,另一个使用了两个无名信号量来实现。
验证

两段代码结果打印的都是相同的信息
4、有名信号量 用于进程间同步和互斥
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
void my_printf(char *str)
{int i = 0;while (str[i] != '\0'){printf("%c", str[i++]);fflush(stdout);sleep(1);}
}int main(int argc, char **argv)
{// 创建2个有名信号量sem_open 最后一个参数为初始值sem_t *sem1 = sem_open("sem1", O_RDWR | O_CREAT, 0666, 1);sem_t *sem2 = sem_open("sem2", O_RDWR | O_CREAT, 0666,0);// psem_wait(sem1);// 任务my_printf("this is sem1\n");// vsem_post(sem2);// 任务my_printf("this is sem2\n");//psem_post(sem1);// 关闭信号量sem_close(sem1);sem_close(sem2);// 销毁信号量sem_destroy(sem1);sem_destroy(sem2);return 0;
}
进程通过sem_wait(sem1)获取sem1信号量,如果此时sem1的值为1,则将其减1,表示占用资源,否则阻塞等待。进程输出"this is sem1\n",此时其他进程无法获得sem1信号量,从而实现了互斥。进程通过sem_post(sem2)释放sem2信号量,将其值加1,唤醒其他阻塞在sem2上的进程,从而实现了同步。此时其他进程可以获取sem2信号量,进行下一步操作。进程输出"this is sem2\n",其他进程无法获取sem2信号量,从而实现了互斥。
在第二个任务执行完毕之后,调用sem_post(sem1)将sem1的值加一,此时又可以重新进入第一个任务。这种方式可以保证第一个任务和第二个任务的执行顺序,并且在第二个任务执行完毕之前第一个任务不会被重新执行。同时,由于sem1的初始值为1,保证了只有一个进程可以访问第一个任务的代码,实现了互斥
验证

相关文章:
同步与互斥之信号量
目录 1、信号量用于线程的互斥 验证 2、信号量用于线程的同步 验证 3、无名信号量用于进程间互斥 代码一 代码二 验证 4、有名信号量 用于进程间同步和互斥 验证 信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它…...
如何当个优秀的文档工程师?从 TC China 看技术文档工程师的自我修养
本文系 NebulaGraph Community Academic 技术文档工程师 Abby 的参会观感,讲述了她在中国技术传播大会分享的收获以及感悟。 据说,技术内容领域、传播领域的专家和决策者们会在中国技术传播大会「tcworld China 2022」大会上分享心得。作为一名技术文档工…...
如何学习k8s
学习Kubernetes可以遵循以下步骤: 了解Kubernetes的基本概念和架构。学习Kubernetes前,需要了解它的基本概念和组成部分,包括Pod、Service、ReplicaSet、Deployment、Namespace等等,同时也需要了解Kubernetes的整体架构和工作原理…...
【SSM】MyBatis(十.动态sql)
文章目录1.if2.where3.trim4.set5. choose when otherwise6.foreach6.1 批量删除6.2 批量增加7.sql1.if <select id"selectByMultiCondition" resultType"Car">select * from t_car where 1 1<if test"brand ! null and brand ! ">…...
最近很多人都在说 “前端已死”,讲讲我的看法
转自 : 掘金 作者 : Ethan_Zhou 现状 我记得去年脉脉的论调还都是 客户端已死,前后端还都是一片祥和,有秀工资的,有咨询客户端转前端的,怎么最近打开脉脉一看,风向变了? 随便刷几下,出来的信息…...
大家好,我是火旺技术
大家好,我是火旺技术 在Internet高速发展的今天,我们生活的各个领域都涉及到计算机的应用。这其中,家乡特色推荐的网络应用已经成为外国家乡推荐系统的一种很普遍的方式。不过,在国内,管理网站可能还处于起步阶段。 …...
【Java并发编程系列】全方位理解多线程几乎包含线程的所有操作哦
文章目录一、概述及目录二、实现多线程的方式2.1 继承Tread类,重写run方法。start方法2.2 实现Runnable方法,并实现run接口方法2.3 实现Callable接口重写call方法,Feature.get()获取返回值三、线程的执行流程3.1 执行流程3.2 start方法和 run…...
天宝S6测量机器人/天宝S6全站仪参数/教程/Trimble 天宝全站仪
TRIMBLE DR PLUS技术 Trimble DR Plus™距离测量技术实现更大范围的直接反射测量,不使用棱镜也能进行长距离测量。难以抵达或不安全的测 量目标,对Trimble S6来说不再是问题。Trimble DR Plus结合 了MagDrive™磁驱伺服技术,使测量的快捷和…...
c++基础知识汇总
目录 1、基础 1.2 注释 1.3 变量 1.4 常量 1.5 关键字 1.6 标识符命名规则 2 数据类型 2.1 整型 2.2 sizeof关键字 2.3 实型(浮点型) 2.4 字符型 2.5 转义字符 2.6 字符串型 2.7 布尔类型 bool 2.8 数据的输入 1、基础 1.2 注释 作用&a…...
重磅!基于GPT-4的全新智能编程助手 GitHub Copilot X 来了!
GitHub Copilot相信大家一定不陌生了,强大的智能代码补全功能一度让媒体直呼程序员要被替代。随着OpenAI推出全新的GPT-4,GitHub Copilot也在3月22日,推出了全新一代产品:GitHub Copilot X 。最新的GitHub Copilot X 不仅可以自动…...
第04章_运算符
第04章_运算符 🏠个人主页:shark-Gao 🧑个人简介:大家好,我是shark-Gao,一个想要与大家共同进步的男人😉😉 🎉目前状况:23届毕业生,目前在某公…...
Excel 文件比较工具:xlCompare 11.0 Crack
(Excel 文件比较工具)xlCompare 11.0 下载并安装最新版本的 xlCompare。下载是一个功能齐全的版本。 筛选匹配的行 筛选不同的行 仅显示两个 Excel 文件中存在的行,并排除新(已删除)行 隐藏在另一张工作表上具有相应行…...
802.1x认证原理
802.1x认证原理802.1X认证简介802.1X认证协议802.1X认证流程802.1X认证简介 定义:802.1x协议是一种基于端口的网络接入控制协议。基于端口的网络接入控制,是指在局域网接入设备的端口这一级验证用户身份,并且控制其访问权限。优点࿱…...
GPIO的八种模式分析
GPIO是general purpose input output,即通用输入输出端口,作用是负责外部器件的信息和控制外部器件工作。 GPIO有如下几个特点:1.不同型号的IO口数量不同;2,反转快速,每次翻转最快只需要两个时钟周期,以ST…...
携职教育:财会人常用必备,203个EXCEL快捷键汇总
会用快捷键的人早下班! 作为财务人员如果你想要提高工作效率,不掌握一些Excel快捷键怎么行? 老板看见了,也会赞一声,“看,这就是专业。” 立马给你加薪(划掉),工作效率这…...
【美赛】2023年ICM问题Z:奥运会的未来(思路、代码)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
CSS基础入门
CSS基础之语法 介绍 CSS(层叠样式表)是一门用来设计网页样式的语言,如网页的布局、字体、颜色搭配、视觉特效。作为WEB开发的基础技术之一,掌握CSS的语法和API对于我们构建丰富的网页是必须的。 基础语法 <style>div …...
可重入锁、读写锁、邮戳锁 详解
文章目录1、可重入锁(递归锁)2、读写锁2.1、读写分离2.2、从写锁到读锁,ReentrantReadWriteLock可以降级2.3、写锁和读锁是互斥的3、邮戳锁StampedLock3.1、是什么3.2、锁饥饿3.3、如何缓解锁饥饿问题呢3.3.1、使用“公平”策略3.3.2、Stampe…...
HBase客户端、服务器端、列簇设计、HDFS相关优化,HBase写性能优化切入点,写异常问题检查点
HBase客户端、服务器端、列簇设计、HDFS相关优化,HBase写性能优化切入点,写异常问题检查点HBase读优化1.1 HBase客户端优化1) scan缓存是否设置合理?2) get请求是否可以使用批量请求?3) 请求是否可以显示指定列簇或者列࿱…...
DINO-DETR在COCO缩减数据集上实验结果分析
博主在进行DINO-DETR模型实验时,使用缩减后的COCO数据集进行训练,发现其mAP值只能达到0.27作用,故而修改了下pycocotool的代码,令其输出每个类别的AP值,来看看是由于什么原因导致这个问题。 之所以这样是因为博主认为各…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
