用于实现进程间的同步与互斥(进程/线程安全概念),保证进程间对临界资源的安全有序访问。
多个进程同时操作一个临界资源的时候就需要通过同步与互斥机制来实现临界资源的安全访问
同步:保证对临界资源访问的时序的可控性
互斥:对临界资源同一时间的唯一访问性
本质:具有一个等待队列的计数器(代表现在还有没有资源使用),当信号量没有资源可用时,这时候需要阻塞等待。
同步:只有信号量资源计数从0转变为1的时候,才会通知别人,打断阻塞等待,去操作临界资源。
互斥:同一时间,A获取了信号量的资源,其它进程就没有办法获取资源了。
二元信号量:信号量如果想要实现互斥,那么它的计数器只能是0或1
*P操作: 获取信号量资源说的是对计数器进行-1操作
*V操作:释放信号量资源说的是对计数器进行+1操作
进程在操作临界资源之前先获取信号量资源,判断是否可以对临界资源进行操作,如果信号量没有资源了(计数器为0),则需要等待,当别人释放信号量计数器变为1,才会唤醒等待的进程去重新获取信号量资源。
-
信号量数据大于0,代表信号量有资源,可以操作,
信号量资源等于0,代表信号量没有资源,需要等待。 -
信号量作为进程间通讯方式,意味着大家都能访问到信号量,实际上也是一个临界资源,但是信号量的这个临界资源的操作是不会出问题的,因为信号量的操作是一个原子操作。
-
创建信号量
int semget(key_t key, int nsems, int semflg);
key: IPC_KEY标识
nsems:指定这次要创建的信号量个数
semflg:IPC_CREAT|IPC_EXCL|0664
返回值:操作信号量句柄
失败:-1
- 设置信号量初值
int semctl(int semid, int semnum, int cmd, ...);
semid:信号量句柄
semnum:指定操作的是第几个信号量
cmd:SETVAL设置单个信号量的初值,SETALL设置所有信号量的初值,semnum将被忽略,IPC_RMID删除信号量,…将被NULL
成功返回值:0
失败返回值:-1
...
是一个不定参数:
对于不同的操作 | 函数会对对应的操作的联合结构中的对象操作 |
---|---|
设置单个信号量的初值 | int val; /* Value for SETVAL */ |
设置所有信号量的初值 | unsigned short *array; /* Array for GETALL, SETALL */ |
union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
- 获取/释放信号量资源
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:信号量句柄
nsops操作的信号量个数
这个sembuf在库里是这样的
struct sembuf
{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
}
PV操作:为了方便操作获取、释放资源,我们封装了两个函数
获取资源:
void sem_P(int id)
{
struct sembuf buf;
buf.sem_num = 0; //信号量编号,因为下面的操作值创建了一个信号量,编号为0
buf.sem_op = -1; //信号量操作,获取资源,信号量资源-1
buf.sem_flg = SEM_UNDO; //SEM_UNDO,如果该进程意外退出,则会自动释放该资源
semop(id, &buf, 1);
}
释放资源:
void sem_V(int id)
{
struct sembuf buf;
buf.sem_num = 0; //信号量编号,因为下面的操作值创建了一个信号量,编号为0
buf.sem_op = 1; //信号量操作,释放资源,信号量资源+1
buf.sem_flg = SEM_UNDO; //SEM_UNDO,如果该进程意外退出,则会自动释放该资源
semop(id, &buf, 1);
}
- 删除信号量
semctl(semid, 0,IPC_RMID,NULL);
信号量实现同步
//信号量实现同步
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/ipc.h>
#include<errno.h>
#include<sys/sem.h>
#define IPC_KEY 0x999
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void sem_P(id)
{
struct sembuf buff;
buff.sem_num=0;
buff.sem_op=-1;
buff.sem_flg=SEM_UNDO;
semop(id,&buff,1);
}
void sem_V(id)
{
struct sembuf buff;
buff.sem_num=0;
buff.sem_op=1;
buff.sem_flg=SEM_UNDO;
semop(id,&buff,1);
}
int main()
{
umask(0);
int semid = semget(IPC_KEY,1,IPC_CREAT|0644);
if(semid<0){
perror("semget error");
return -1;
}
union semun val;
//设置信号量初值int semctl(int semid, int semnum, int cmd, ...);
val.val=0;
semctl(semid,0,SETVAL,val);
int pid = -1;
pid=fork();
if(pid<0){
perror("pid error");
return -1;
}else if(pid==0)
{
//子进程去获取资源,吃方便面
while(1)
{
sem_P(semid);
printf("我吃了一包方便面\n");
sleep(1);
}
}
else{
while(1){
sem_V(semid);
printf("我制造了一包方便面\n");
sleep(1);
}
}
semctl(semid,0,IPC_RMID,NULL);
}
信号量实现互斥
这是一个基于信号量的互斥操作, 让子进程打印A睡1000us然后再打印一个A 让父进程打印B睡1000us然后再打印一个B
检查结果是否是连续的?
如何让打印结果是我们预期的AA BB这种形式
关键点就在于两个进程的打印操作都不能被打断,这时候就需要使用一个一元信号量来完成互斥操作
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/ipc.h>
#include <sys/sem.h>
//定义IPC标识
#define IPC_KEY 0x666
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void sem_P(int id)
{
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=-1;
buf.sem_flg=SEM_UNDO;
semop(id,&buf,1);
}
void sem_V(int id)
{
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=1;
buf.sem_flg=SEM_UNDO;
semop(id,&buf,1);
}
int main()
{
umask(0);
//1、创建信号量int semget(key_t key, int nsems, int semflg);
int semid=-1;
semid=semget(IPC_KEY,1,IPC_CREAT|0664);
if(semid<0){
perror("semget error");
return -1;
}
union semun godo;
godo.val=1;
//2、设置信号量初值int semctl(int semid, int semnum, int cmd, ...);
semctl(semid,0,SETVAL,godo);
//创建子进程
int pid=-1;
pid=fork();
if(pid<0){
perror("fork error");
return -1;
}else if(pid==0){
//子进程获取信号量,打印A,睡1000us再打印A,再释放资源,轮回
while(1)
{
//获取资源
sem_P(semid);
printf("A");
fflush(stdout);
usleep(1000);
printf("A");
fflush(stdout);
//释放资源
sem_V(semid);
}
}else{
//父进程
while(1)
{
sem_P(semid);
printf("B");
fflush(stdout);
usleep(1000);
printf("B ");
fflush(stdout);
//释放资源
sem_V(semid);
}
}
//删除信号量
semctl(semid,0,IPC_RMID,NULL);
return 0;
}
- 将二元信号量P/V操作,封装成动态/静态库,并分别使用并测试
-
生产者消费者原理
- 同步:生产者消费者问题,主要是通过生产者生产出产品放入缓冲区,然后消费者从缓冲区中拿出产品消费。
- 互斥:生产者和消费者不能同时在临界区进行操作,同一时间只能有一个生产者或者一个消费者在临界区进行操作。