当前位置: 首页 > news >正文

同步与互斥之信号量

目录

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 用于实现互斥访问。三个线程 tid1tid2tid3 将同时运行,它们的目标是调用 my_printf 函数打印不同的字符串,但是这些线程之间需要互斥访问共享资源,否则会导致输出的字符串混乱。

        在 fun1fun2fun3 函数中,首先调用 sem_wait 函数来申请信号量资源,表示进入临界区,如果信号量的值为1,则将其减1,表示申请成功,否则阻塞等待。然后调用 my_printf 函数输出相应的字符串,最后调用 sem_post 函数释放信号量资源,将其加1,表示退出临界区,其他线程就可以申请这个资源了。

        主函数中,初始化了信号量,创建三个线程,分别调用 fun1fun2fun3 函数,最后等待三个线程执行结束,销毁信号量。

验证

         线程的执行顺序是不确定的,由操作系统决定。虽然代码中是先创建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_waitsem_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;}

        这段代码定义了两个无名信号量sem1sem2,并使用mmap将它们分别映射到了进程的虚拟内存空间中。然后使用这两个无名信号量实现了进程间的同步。不同于前面那段代码,这段代码使用了两个不同的指针变量sem1sem2分别指向映射到内存中的两个无名信号量。同时在创建子进程时,子进程通过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、有名信号量 用于进程间同步和互斥 验证 信号量广泛用于进程或线程间的同步和互斥&#xff0c;信号量本质上是一个非负的整数计数器&#xff0c;它…...

如何当个优秀的文档工程师?从 TC China 看技术文档工程师的自我修养

本文系 NebulaGraph Community Academic 技术文档工程师 Abby 的参会观感&#xff0c;讲述了她在中国技术传播大会分享的收获以及感悟。 据说&#xff0c;技术内容领域、传播领域的专家和决策者们会在中国技术传播大会「tcworld China 2022」大会上分享心得。作为一名技术文档工…...

如何学习k8s

学习Kubernetes可以遵循以下步骤&#xff1a; 了解Kubernetes的基本概念和架构。学习Kubernetes前&#xff0c;需要了解它的基本概念和组成部分&#xff0c;包括Pod、Service、ReplicaSet、Deployment、Namespace等等&#xff0c;同时也需要了解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 现状 我记得去年脉脉的论调还都是 客户端已死&#xff0c;前后端还都是一片祥和&#xff0c;有秀工资的&#xff0c;有咨询客户端转前端的&#xff0c;怎么最近打开脉脉一看&#xff0c;风向变了&#xff1f; 随便刷几下&#xff0c;出来的信息…...

大家好,我是火旺技术

大家好&#xff0c;我是火旺技术 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用。这其中&#xff0c;家乡特色推荐的网络应用已经成为外国家乡推荐系统的一种很普遍的方式。不过&#xff0c;在国内&#xff0c;管理网站可能还处于起步阶段。 …...

【Java并发编程系列】全方位理解多线程几乎包含线程的所有操作哦

文章目录一、概述及目录二、实现多线程的方式2.1 继承Tread类&#xff0c;重写run方法。start方法2.2 实现Runnable方法&#xff0c;并实现run接口方法2.3 实现Callable接口重写call方法&#xff0c;Feature.get()获取返回值三、线程的执行流程3.1 执行流程3.2 start方法和 run…...

天宝S6测量机器人/天宝S6全站仪参数/教程/Trimble 天宝全站仪

TRIMBLE DR PLUS技术 Trimble DR Plus™距离测量技术实现更大范围的直接反射测量&#xff0c;不使用棱镜也能进行长距离测量。难以抵达或不安全的测 量目标&#xff0c;对Trimble S6来说不再是问题。Trimble DR Plus结合 了MagDrive™磁驱伺服技术&#xff0c;使测量的快捷和…...

c++基础知识汇总

目录 1、基础 1.2 注释 1.3 变量 1.4 常量 1.5 关键字 1.6 标识符命名规则 2 数据类型 2.1 整型 2.2 sizeof关键字 2.3 实型&#xff08;浮点型&#xff09; 2.4 字符型 2.5 转义字符 2.6 字符串型 2.7 布尔类型 bool 2.8 数据的输入 1、基础 1.2 注释 作用&a…...

重磅!基于GPT-4的全新智能编程助手 GitHub Copilot X 来了!

GitHub Copilot相信大家一定不陌生了&#xff0c;强大的智能代码补全功能一度让媒体直呼程序员要被替代。随着OpenAI推出全新的GPT-4&#xff0c;GitHub Copilot也在3月22日&#xff0c;推出了全新一代产品&#xff1a;GitHub Copilot X 。最新的GitHub Copilot X 不仅可以自动…...

第04章_运算符

第04章_运算符 &#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目前在某公…...

Excel 文件比较工具:xlCompare 11.0 Crack

&#xff08;Excel 文件比较工具&#xff09;xlCompare 11.0 下载并安装最新版本的 xlCompare。下载是一个功能齐全的版本。 筛选匹配的行 筛选不同的行 仅显示两个 Excel 文件中存在的行&#xff0c;并排除新&#xff08;已删除&#xff09;行 隐藏在另一张工作表上具有相应行…...

802.1x认证原理

802.1x认证原理802.1X认证简介802.1X认证协议802.1X认证流程802.1X认证简介 定义&#xff1a;802.1x协议是一种基于端口的网络接入控制协议。基于端口的网络接入控制&#xff0c;是指在局域网接入设备的端口这一级验证用户身份&#xff0c;并且控制其访问权限。优点&#xff1…...

GPIO的八种模式分析

GPIO是general purpose input output,即通用输入输出端口&#xff0c;作用是负责外部器件的信息和控制外部器件工作。 GPIO有如下几个特点&#xff1a;1.不同型号的IO口数量不同&#xff1b;2&#xff0c;反转快速&#xff0c;每次翻转最快只需要两个时钟周期&#xff0c;以ST…...

携职教育:财会人常用必备,203个EXCEL快捷键汇总

会用快捷键的人早下班&#xff01; 作为财务人员如果你想要提高工作效率&#xff0c;不掌握一些Excel快捷键怎么行&#xff1f; 老板看见了&#xff0c;也会赞一声&#xff0c;“看&#xff0c;这就是专业。” 立马给你加薪&#xff08;划掉&#xff09;&#xff0c;工作效率这…...

【美赛】2023年ICM问题Z:奥运会的未来(思路、代码)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

CSS基础入门

CSS基础之语法 介绍 ​CSS&#xff08;层叠样式表&#xff09;是一门用来设计网页样式的语言&#xff0c;如网页的布局、字体、颜色搭配、视觉特效。作为WEB开发的基础技术之一&#xff0c;掌握CSS的语法和API对于我们构建丰富的网页是必须的。 基础语法 <style>div …...

可重入锁、读写锁、邮戳锁 详解

文章目录1、可重入锁&#xff08;递归锁&#xff09;2、读写锁2.1、读写分离2.2、从写锁到读锁&#xff0c;ReentrantReadWriteLock可以降级2.3、写锁和读锁是互斥的3、邮戳锁StampedLock3.1、是什么3.2、锁饥饿3.3、如何缓解锁饥饿问题呢3.3.1、使用“公平”策略3.3.2、Stampe…...

HBase客户端、服务器端、列簇设计、HDFS相关优化,HBase写性能优化切入点,写异常问题检查点

HBase客户端、服务器端、列簇设计、HDFS相关优化&#xff0c;HBase写性能优化切入点&#xff0c;写异常问题检查点HBase读优化1.1 HBase客户端优化1) scan缓存是否设置合理&#xff1f;2) get请求是否可以使用批量请求&#xff1f;3) 请求是否可以显示指定列簇或者列&#xff1…...

DINO-DETR在COCO缩减数据集上实验结果分析

博主在进行DINO-DETR模型实验时&#xff0c;使用缩减后的COCO数据集进行训练&#xff0c;发现其mAP值只能达到0.27作用&#xff0c;故而修改了下pycocotool的代码&#xff0c;令其输出每个类别的AP值&#xff0c;来看看是由于什么原因导致这个问题。 之所以这样是因为博主认为各…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Caliper 负载(Workload)详细解析

Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

WebRTC从入门到实践 - 零基础教程

WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC&#xff1f; WebRTC&#xff08;Web Real-Time Communication&#xff09;是一个支持网页浏览器进行实时语音…...