九、进程间通信-信号量
一、概述
1.信号量
信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
2.信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
(1)P(sv)
如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
(2)V(sv)
如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)
注:原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的
3、作用
- 互斥
- 同步
- 若要在进程间传递数据需要结合共享内存
4、二元信号量
二元信号量(Binary Semaphore)是最简单的一种锁(互斥锁),它只用两种状态:占用与非占用。所以它的引用计数为1。
5.进程如何获得共享资源
(1)测试控制该资源的信号量
(2)信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位
(3)若此时信号量的值为0,则进程进入挂起状态(进程状态改变),直到信号量的值大于0,若进程被唤醒则返回至第一步。
注:信号量通过同步与互斥保证访问资源的一致性。
二、信号量的用法
1、定义一个唯一key(ftok)
1 2 3 4 | #include <sys/types.h> #include <sys/ipc.h> key_t ftok( const char *pathname, int proj_id); |
2、构造一个信号量(semget)
1 | int semget(key_t key, int nsems, int semflg) //获取信号量ID |
(1)参数:
- key:信号量键值
- nsems:信号量数量
- semflg:
- IPC_CREATE:信号量不存在则创建
- mode:信号量的权限
(2)返回值:
-
成功:信号量ID.
-
失败:-1
3、初始化信号量(semctl SETVA)
1 | int semctl( int semid, int semnum, int cmd, union semun arg) |
(1)参数
-
semid:信号量ID
-
semnum:信号量编号
-
cmd:
-
IPC_STAT:获取信号量的属性信息
-
IPC_SET:设置信号量的属性
-
IPC_RMID:删除信号量
-
IPC_SETVAL:设置信号量的值
-
- arg:
1 2 3 4 5 | union semun { int val; struct semid_ds *buf; } |
(2)返回值
-
成功:由cmd类型决定。
-
失败:-1
4、对信号量进行P/V操作(semop)
1 | int semop( int semid, struct sembuf *sops, size_t nsops) |
(1)参数
-
semid :信号量ID
- sops:信号量操作结构体数组
1 2 3 4 5 6 | struct sembuf { short sem_num; //信号量编号 short sem_op; //信号量P/V操作 short sem_flg; //信号量行为,SEM_UNDO } |
-
nsops:信号量数量
(2)返回值
-
成功:0
-
失败: -1
5、删除信号量(semctl RMID)
1 2 3 4 5 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> //信号量控制操作 int semctl( int semid, int semnum, int cmd, ...); |
(1)参数
-
semid:信号量ID.
-
semnum:信号量数量
-
cmd:
-
IPC_STAT : 读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
-
IPC_SET : 设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
-
IPC_RMID : 将信号量集从内存中删除。
-
IPC_INFO : 返回有关系统范围的信号量限制和参数的信息
-
GETALL :用于读取信号量集中的所有信号量的值
-
GETNCNT :返回正在等待资源的进程数目。
-
GETPID : 返回最后一个执行semop操作的进程的PID。
-
GETVAL : 返回信号量集中的一个单个的信号量的值。
-
GETZCNT : 返回正在等待完全空闲的资源的进程数目。
-
SETALL : 设置信号量集中的所有的信号量的值。
-
SETVAL : 设置信号量集中的一个单独的信号量的值。
-
(2)返回值:
- 失败:返回-1
- 成功:非负值,返回值由cmd决定
三、实例
信号量用于子进程和父进程的同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | #include<stdio.h> #include<string.h> #include<stdlib.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/sem.h> #define DELAY_TIME 3 union semun{ int val; struct seimd_ds *buf; }; /*初始化信号量*/ int init_sem( int sem_id, int init_value) { union semun sem_union; sem_union.val = init_value; /*设置信号量集中的一个单独的信号量的值*/ if (semctl(sem_id,0,SETVAL,sem_union) == -1) { printf ( "Initialize semaphore failed!\n" ); return -1; } return 0; } /*删除信号量*/ int del_sem( int sem_id) { union semun sem_union; if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) { perror ( "Delete semaohore fail!\n" ); return -1; } return 0; } /*P 操作:执行P操作时,信号量-1*/ int sem_p( int sem_id) { struct sembuf sops; sops.sem_num = 0; sops.sem_op = -1; sops.sem_flg = SEM_UNDO; /*系统自动释放将会在系统中残留的信号量*/ if (semop(sem_id,&sops,1) == -1) { perror ( "P operation failed\n" ); return -1; } return 0; } /*V 操作:执行P操作时,信号量+1*/ int sem_v( int sem_id) { struct sembuf sops; sops.sem_num = 0; /*单个信号量的编号应该为 0*/ sops.sem_op = 1; /*表示V操作*/ sops.sem_flg = SEM_UNDO; /*系统自动释放将会在系统中残留的信号量*/ if (semop(sem_id,&sops,1) == -1) { perror ( "V operation failed\n" ); return -1; } return 0; } void main( void ) { pid_t result; int sem_id; sem_id = semget((key_t)6666,1,0666|IPC_CREAT); /*创建一个信号量*/ init_sem(sem_id,0); //初始化信号量的值为0,只能先执行V操作+1,然后才能执行P操作-1 /*调用fork()函数*/ result = fork(); if (result == -1) { perror ( "Fork\n" ); } else if (result == 0) { printf ( "Child proess will wait for some seconds...\n" ); sleep(DELAY_TIME); printf ( "the child process is running...\r\n" ); sem_v(sem_id); //如果有其他进程因等待信号量而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1. } else /*返回值大于0代表父进程*/ { sem_p(sem_id); //如果信号量等于0,则进程挂起,直到信号量大于0,执行V操作 printf ( "the father process is running...\r\n" ); del_sem(sem_id); } exit (0); } |
执行结果:
由于信号量的值初始化为0,所以父进程一致挂起不能进行P操作,等待子进程执行完毕V操作后,信号量+1,此时信号量大于0,可以进行P操作执行父进程。执行完父进程后删除信号量。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了