进程间通信——信号量
目录
一、基本概念
1.1 概念
1.2 基本操作
1.3 相关函数
1.3.1 semget创建/获取
1.3.2 semop操作信号量
1.3.3 semctl初始化/删除
二、代码操作
2.1 不用PV的
2.2 用PV 的
2.2.1 a.c
2.2.2 b.c
2.2.3 sem.h
2.2.4 sem.c
一、基本概念
1.1 概念
信号量本质上是一个计数器,用于记录可用资源的数量。它有两种类型的操作:P 操作(也称为 wait 操作)和 V 操作(也称为 signal 操作)。P 操作会将信号量的值减 1,表示请求一个资源;V 操作会将信号量的值加 1,表示释放一个资源。
当信号量的值为 0 时,表示没有可用资源,此时进程在请求资源时会被阻塞,直到其他进程释放资源。
临界资源:同一时刻只允许一个进程访问的资源
临界区:访问临界资源的代码段。这段代码执行了就会访问例如打印机之类的临界资源
1.2 基本操作
初始化:在使用信号量之前,需要对其进行初始化,设置初始值。这个值通常表示系统中某种资源的初始数量。
P 操作:当一个进程需要访问共享资源时,它首先执行 P 操作。如果信号量的值大于 0,那么 P 操作会成功执行,进程可以继续执行并使用资源,同时信号量的值减 1。如果信号量的值为 0,那么进程会被阻塞,放入与该信号量相关的等待队列中,直到其他进程执行 V 操作释放资源。
V 操作:当进程使用完共享资源后,它执行 V 操作。V 操作会将信号量的值加 1,如果有其他进程在等待该资源(即信号量的等待队列不为空),那么系统会从等待队列中唤醒一个进程,让它继续执行 P 操作以获取资源。
1.3 相关函数
1.3.1 semget创建/获取
semget
函数用于创建一个新的信号量集或获取一个已存在的信号量集的标识符
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);
key
:一个键值,用于标识信号量集。可以使用ftok
函数生成一个唯一的键值,也可以使用特殊值IPC_PRIVATE
来创建一个私有的信号量集,该信号量集只能由创建它的进程及其子进程访问。nsems
:指定信号量集中信号量的数量。如果是创建新的信号量集,必须指定该值;如果是获取已存在的信号量集,该值通常为 0。semflg
:标志位,用于指定创建信号量集的模式和权限等。常见的标志有:
IPC_CREAT
:如果信号量集不存在,则创建它。IPC_EXCL
:与IPC_CREAT
一起使用,若信号量集已存在,则返回错误。- 权限标志,如
0666
表示所有用户都有读写权限。- 成功时,返回一个非负整数,即信号量集的标识符(semid)。
- 失败时,返回 -1,并设置
errno
来指示错误类型。
1.3.2 semop操作信号量
semop
函数用于对信号量集中的一个或多个信号量进行操作,如 P 操作(申请资源)和 V 操作(释放资源)
#include <sys/sem.h>int semop(int semid, struct sembuf *sops, unsigned nsops);
semid
:信号量集的标识符,由semget
函数返回。sops
:指向一个struct sembuf
结构体数组的指针,每个struct sembuf
结构体描述了对一个信号量的操作。struct sembuf
结构体的定义如下:struct sembuf {unsigned short sem_num; // 信号量在信号量集中的索引(从 0 开始)short sem_op; // 操作类型,正数表示释放资源(V 操作),负数表示申请资源(P 操作),0 表示等待信号量值为 0short sem_flg; // 标志位,常见的有 IPC_NOWAIT 表示非阻塞操作 };
nsops
:sops
数组中元素的数量,即要执行的操作数量。
1.3.3 semctl初始化/删除
semctl
函数用于对信号量集进行控制操作,如初始化信号量的值、获取信号量的状态信息、删除信号量集等。
#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);
semid
:信号量集的标识符,由semget
函数返回。semnum
:信号量在信号量集中的索引(从 0 开始)。如果cmd
不需要指定特定的信号量,则该值通常为 0。cmd
:要执行的控制命令,常见的命令有:
SETVAL
:设置指定信号量的值,需要传递一个int
类型的参数作为新值。GETVAL
:获取指定信号量的值,返回值即为信号量的当前值。IPC_RMID
:删除信号量集,不需要指定semnum
,也不需要额外的参数。...
:可变参数,根据cmd
的不同而有所不同。例如,当cmd
为SETVAL
时,需要传递一个int
类型的参数作为新的信号量值。
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "sem.h"int main()
{sem_init(); // 创建并初始化信号量for(int i=0; i<5; i++){sem_p();printf("A");fflush(stdout);int n = rand() % 3; // 随机睡眠一段时间sleep(n);printf("A");fflush(stdout);sem_v();n = rand() % 3;sleep(n);}sleep(10);sem_destroy(); // 删除信号量
}
二、代码操作
2.1 不用PV的
a.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>int main()
{for(int i=0;i<5;i++){printf("A");fflush(stdout);int n=rand()%3;sleep(n);printf("A");fflush(stdout);}
}
b.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>int main()
{for(int i=0;i<5;i++){printf("B");fflush(stdout);int n=rand()%3;sleep(n);printf("B");fflush(stdout);}
}
/a &./b&
:在后台同时运行可执行文件a
和b
,[1] 10916
和[2] 10917
分别是这两个后台进程的作业编号和进程号。
2.2 用PV 的
2.2.1 a.c
a.c使用信号量进行进程同步的C语言程序示例。
它演示了如何使用信号量来控制对共享资源的访问,以防止数据竞争和条件竞争
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "sem.h"int main()
{sem_init();//创建并初始化信号量//pfor(int i = 0; i < 5; i++ ){sem_p();printf("A");fflush(stdout);int n = rand() % 3;sleep(n);printf("A");fflush(stdout);sem_v();n = rand()%3;sleep(n);}//vsleep(10);sem_destroy();//删除信号量
}
2.2.2 b.c
b.c它演示了如何使用信号量来控制对共享资源的访问。
在这个例子中,程序通过信号量来确保在打印字符'B'时不会有多个线程或进程同时执行,从而避免输出混乱。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "sem.h"
int main()
{sem_init();for(int i = 0; i < 5; i++ ){sem_p();printf("B");fflush(stdout);int n = rand() % 3;sleep(n);printf("B");fflush(stdout);sem_v();n = rand()%3;sleep(n);}}
2.2.3 sem.h
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>union semun
{int val;
};void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
定义了一组与信号量操作相关的函数原型和一个用于信号量操作的联合体
semun
这个联合体用于在信号量操作中传递信号量的值
这些函数原型定义了信号量操作的接口,下面是这些函数可能的实现方式:
sem_init
:初始化信号量。
sem_p
:执行信号量的P操作(wait操作),减少信号量的值,如果信号量的值小于0,则进程将被阻塞。
sem_v
:执行信号量的V操作(signal操作),增加信号量的值,如果有进程因等待此信号量而被阻塞,则它们可能会被唤醒。
sem_destroy
:销毁信号量,释放相关资源。
2.2.4 sem.c
信号量操作实现
#include "sem.h"
static int semid = -1;void sem_init()
{semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//尝试创建一个新的信号了if( semid == -1 )//信号量可能存在{semid = semget((key_t)1234,1,0600);if( semid == -1){printf("sem err\n");return;}}else//初始化 1{union semun a; a.val = 1;if( semctl(semid,0,SETVAL,a) == -1){printf("semctl err\n");}}
}
void sem_p()
{struct sembuf buf;buf.sem_num = 0;buf.sem_op = -1;//pbuf.sem_flg = SEM_UNDO;//if( semop(semid,&buf,1) == -1)//可能阻塞{printf("p err\n");}}
void sem_v()
{struct sembuf buf;buf.sem_num = 0;buf.sem_op = 1;//vbuf.sem_flg = SEM_UNDO;//if( semop(semid,&buf,1) == -1){printf("v err\n");}
}
void sem_destroy()
{if( semctl(semid,0,IPC_RMID) == -1){printf("rm semid err\n");}
}
sem_init
函数 初始化一个信号量