进程间通信(管道、信号量、共享内存、消息队列、套接字)
可以类比于红绿灯,对于路口这个共享的通行权,谁得到红绿灯的通行信号,才可以得到路口的通行权,没得到通信信号,就只能等待。
信号量是一个原子操作,例如+1、-1,不能被打断,只有等其操作完才能去使用。这里的+1 、 -1类比于资源的释放和获取,为二值信号量,只有两个值,其初始值为1,就只有0和1两个值。
信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目, 获取资源时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0时,代表没有资源可用, P 操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为 V操作。信号量主要用来同步进程。 信号量的值如果只取 0,1,将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量。注意:正数值; 加一减一是一个原子操作;
临界资源:计算机的软硬件资源,即同一时刻,只允许一个进程或者线程访问的资源;
临界区:访问临界资源的代码段;
不加控制模拟使用打印机:
比如:进程 a 和进程 b 模拟访问打印机,进程 a 输出第一个字符‘ a’表示开始使用打印机,输出第二个字符‘ a’表示结束使用, b 进程操作与 a 进程相同。(由于打印机同
一时刻只能被一个进程使用,所以输出结果不应该出现 abab这样交替的结果)
代码示例:
a.c
//a.c
#include
#include
#include
#include
#include
int main()
{
int i=0;
for(;i<5;i++)
{
printf("A");
fflush(stdout);
int n=rand()%3;
sleep(n);
printf("A");
fflush(stdout);
n=rand()%3;
sleep(n);
}
}
b.c:
//b.c
#include
#include
#include
#include
#include
int main()
{
int i=0;
for(;i<5;i++)
{
printf("B");
fflush(stdout);
int n=rand()%3;
sleep(n);
printf("B");
fflush(stdout);
n=rand()%3;
sleep(n);
}
}
参数:
int semget(key_t key,int nsems,int semflg);
(key_t 其实为int类型,只不过)
key:给相同的key值,能得到相同的信号量。
nsems:创建几个信号量;
semflg:标志位,如果为创建:IPC_CREAT;
–如果为创建:为IPC_CREAT;
–如果为全新创建,也就是不知道是否有人创建过,则 IPC_CREATE | IPC_EXCL ,就是如果没有则创建,如果有则创建失败;
p操作为获取资源;
v操作为释放资源;
参数:
int semop(int semid,struct sembuf *sops,unsigned nsops);
nsops :为结构体长度
semid:信号量的id号,也就是刚才semget的返回值;4;说明对哪个信号量进行操作;
sops:结构体指针,指向sembuf的结构体指针,
sembuf 结构体有三个成员变量:
sem_num 表示信号量的编号(即指定信号量集中的 信号量下标);
sem_op 表示是p还是v操作;1为v操作(加1),-1为p操作(减1);
sem_flg 为标志位;
参数:
int semctl(int semid,int semnum,int cmd,...);
semid:信号量id;
semnum:信号量编号;
cmd:命令:SETVAL:初始化信号量; IPC_RMID:删除信号量;
注意:联合体semun,这个联合体需要自己定义;
思路:
a.c :
#include
#include
#include
#include
#include
int main()
{
int i = 0;
for(;i < 5;i++)
{
printf("A");
fflush(stdout);
int n = rand()%3;
sleep(n);
printf("A");
fflush(stdout);
n = rand()%3;
sleep(n);
}
}
b.c :
#include
#include
#include
#include
#include
int main()
{
int i = 0;
for(;i < 5;i++)
{
printf("B");
fflush(stdout);
int n = rand()%3;
sleep(n);
printf("B");
fflush(stdout);
n = rand()%3;
sleep(n);
}
}
同时运行(加&,后台运行):./a& ./b&
结果不一定完全相同,但都是成块出现,都是两个A两个B
代码实现:
//sem.h
#include
#include
#include
union semun
{
int val;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
//sem.c
#include "sem.h"
static int semid=-1;
//信号量的初始化实现
void sem_init()
{
semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//1234为自己定义的
if(semid==-1)//全新创建失败
{
semid=semget((key_t)1234,1,0600);//获取已经存在的信号量
if(semid==-1)//获取再失败,是真的错了
{
perror("semget error");
}
}
else//全新创建成功
{
//初始化
union semun a;
a.val=1;
if(semctl(semid,0,SETVAL,a)==-1)//初始化,只有一个信号量,所以为0
{
perror("semctl init error");
}
}
}
//p操作
void sem_p()
{
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=-1;//p操作
buf.sem_flg=SEM_UNDO;//相当于操作系统记住你进行了p操作,如果异常结束,无法进行v操作,系统会帮你操作
if(semop(semid,&buf,1)==-1)//出错处理
{
perror("p error");
}
}
//v操作
void sem_v()
{
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=1;//v操作
buf.sem_flg=SEM_UNDO;//相当于操作系统记住你进行了p操作,如果异常结束,无法进行v操作,系统会帮你操作
if(semop(semid,&buf,1)==-1)//出错
{
perror("v error");
}
}
//销毁操作
void sem_destroy()
{
if(semctl(semid,0,IPC_RMID)==-1)//0表示占位
{
perror("destroy sem error");
}
}
//a.c
#include
#include
#include
#include
#include
#include "sem.h"
int main()
{
int i=0;
sem_init();
for(;i<5;i++)
{
//p v 操作之间为临界区,访问临界资源的代码段
sem_p();
printf("A");
fflush(stdout);
int n=rand()%3;
sleep(n);
printf("A");
fflush(stdout);
sem_v();
n=rand()%3;
sleep(n);//sem_v();可以放在此代码之后,但不好,效率过低
}
sleep(10);
sem_destroy();//如果忘记销毁,或者程序出错,信号量可能还在,需要通过ipcs查看
}
//b.c
#include
#include
#include
#include
#include
#include "sem.h"
int main()
{
int i=0;
sem_init();
for(;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);
}
}
思路如下:
运行./a& ./b& 之后
其中4d2,为1234
对应代码semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);