进程间通信(三)—信号量
2016-07-07 15:17
246 查看
我会用几篇博客总结一下在Linux中进程之间通信的几种方法,我会把这个开头的摘要部分在这个系列的每篇博客中都打出来
进程之间通信的方式
管道
消息队列
信号
信号量
共享存储区
套接字(socket)
进程间通信(五)—信号传送门:/article/11898702.html
进程间通信(四)—共享存储区传送门:/article/11898703.html
进程间通信(二)—消息队列传送门:/article/11898705.html
进程间通信(一)—管道传送门:/article/11898706.html
第三篇来了!前两篇访问量很多,真的是很感谢了
这次记下信号量的相关操作函数和方法,和以前一样会在博文的最后把测试代码贴出来!
学过操作系统这本书的话应该对信号量这个名词不会感到陌生,同时信号和信号量是不同的!
信号量多用于进程间的同步与互斥,简单的说一下同步和互斥的意思
同步:处理竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的个先后执行顺序
互斥:互斥访问不可共享的临界资源,同时会引发两个新的控制问题(互斥可以说是特殊的同步)
竞争:当并发进程竞争使用同一个资源的时候,我们就称为竞争进程
简单说一下信号量的工作机制(因为真的很简单),可以直接理解成计数器(当然其实加锁的时候肯定不能这么简单,不只只是信号量了),信号量会有初值(>0),每当有进程申请使用信号量,通过一个P操作来对信号量进行-1操作,当计数器减到0的时候就说明没有资源了,其他进程要想访问就必须等待(具体怎么等还有说法,比如忙等待或者睡眠),当该进程执行完这段工作(我们称之为临界区)之后,就会执行V操作来对信号量进行+1操作。
临界区:不是个简单的区域!是加锁区间的代码!
临界资源:只能被一个进程同时使用(不可以多个进程共享),要用到互斥
我们可以说信号量也是进程间通信的一种方式,比如互斥锁的简单实现就是信号量,一个进程使用互斥锁,并通知(通信)其他想要该互斥锁的进程,阻止他们的访问和使用(老子正在用!@_@!)
其实信号量的操作函数和之前几个都是类似的,都是套路
[b]信号量的创建[/b]
一定会觉得熟悉的!和消息队列十分类似
函数原型:int semget(key_t key, int nsems, int semflg);
头文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
参数解析
key_t这个就不详细解释了,在消息队列篇就讲过了
nsems这个参数是指创造出几个信号量,准确的来说,semget这个函数创建出来一个信号量集(就是包含好多信号量的那种,可以简单的理解为信号量数组),这就是说创建有几个信号量的信号量集,可以选择只创建一个
semflg还是一样的,用到两个参数就够了IPC_CREAT 和 IPC_EXCL怎么用的话我在消息队列篇讲过了,就不多费口舌了
下面会看到信号量的一个缺点
[b]信号量的初始化[/b]
这说明信号量的初始化和创建是分开操作的,为什么是分开的,因为创建的时候没有给信号量的大小的参数,信号量是可以设置大的(不是只可以设置为1),当然设置为1就是互斥访问了,比如典型的生产者和消费者问题,但是如果像是读者写者中的读者信号量可不一样(因为可以有多个读者同时读),这里就不解释这个模型问题了。
缺点就是,一旦初始化和创建分开之后,就会有线程安全的问题,进程A刚创建一信号量还没有初始化,进程B便已经开始对该信号量进程P操作(哥哥!我还没赋值初始化!)根本无法进行-1操作,会阻塞的,我都不知道咋回事(亲身体验该错误)。。。
废话不多说了,函数还是套路
函数原型:int semctl(int semid, int semnum, int cmd, ...);
头文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
参数解析
第一个semid就不多说了标识信号量用的,semget成功时候的返回值就是它
semnum这个就很有用了,刚才说了申请的是一个信号量集,这个参数就是告诉函数要初始化的是第几个信号量,该参数是从0开始的,0表示第一个信号量
cmd这次用这个SETVAL表示设置变量
当SETVAL被设置之后,可变参数列表的第四个参数就可以用上了,这里传入一个神奇的联合体(我在手册里找到了,但是死活用不了它,还是自己写了一个)
就是它!就是它!就看第一个吧,其他我也没用了(英语我也看不太懂),val就是设置的初值啦,传入的时候直接传入就行了,参数就是一个变量(不用传指针)
这个函数一会删除我们还会遇到它,ctl就是control控制的简写,很多操作都有它
[b]信号量操作[/b]
一开始就写了操作的两种方式,一种叫P操作,一种叫V操作,都是通过一个函数实现的,这个函数还关联一个结构体
函数原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
头文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
参数解析
第一个semid老朋友了
第二个就是那个结构体了,贴出来看
第三个表示你想同时操作几个信号量(为什么不是表示index of信号量呢,在结构体里有一起说),因为信号量集可能有很多信号量,每次一个一个操作不现实,这个参数就是提供一个同时操作的
sem_num就是index of信号量,你想操作的信号量的下标,sem_op是个短整型,这里给1或者-1分别表示V和P操作,而最后一个semflg手册中提到了两个
IPC_NOWAIT:一看就是IPC通用的,非等待,不让忙等待只好去休眠(或者别的,反正不能循环等待)
SEM_UNDO:这个一看就是信号量独有的,看英文也能看出来UNDO是取消之前做过的,什么意思呢,不得不提到,当进程在临界区工作(持有锁)的时候,是不允许挂(挂掉更不好啦)起的!!!这很重要(又要加篇幅啦!@_@),因为一旦挂起或者休眠,有可能就醒不了啦!(挂起有可能等待事件)这样不好,会让等待的其他进程饥饿,尤其是挂掉的时候,肯定就更糟糕了,根本就无法执行V操作,等待的进程就会无限的等待下去,递归申请信号量也是不好的,尤其是互斥信号量的时候会造成死锁。这时候UNDO就出来把之前做过的东西都取消掉,P操作就当没执行过,信号量又从0变回了1,皆大欢喜。
[b]信号量的删除[/b]
semctl又来了,和消息队列的删除类似
函数原型:刚才刚写了~!
头文件:刚才刚写了~!
参数解析:刚才刚写了~!
不一样的只是把SETVAL为换成IPC_RMID就行了(当然后面的可变参数列表也不要了,可以看一下测试的例子)
事已至此,基本操作就说完了,废话少说,show me the code
我的程序分为comm.h(公共头文件) comm.c(封装基本函数) server.c(采用fork完成父子进程间使用信号量) 一共3个文件
基本功能:模拟线程安全(简单版本),父进程会打印"A""A"子进程会打印"B""B",因为执行顺序的原因可能会交叉打印,但我不要这样,我要让AA和BB分别连着!完成这个功能
先看之前的版本
交叉打印不是我想要的
功能完成连续打印
[b]comm.h[/b]
[b]comm.c[/b]
[b]server.c[/b]
进程之间通信的方式
管道
消息队列
信号
信号量
共享存储区
套接字(socket)
进程间通信(五)—信号传送门:/article/11898702.html
进程间通信(四)—共享存储区传送门:/article/11898703.html
进程间通信(二)—消息队列传送门:/article/11898705.html
进程间通信(一)—管道传送门:/article/11898706.html
第三篇来了!前两篇访问量很多,真的是很感谢了
这次记下信号量的相关操作函数和方法,和以前一样会在博文的最后把测试代码贴出来!
学过操作系统这本书的话应该对信号量这个名词不会感到陌生,同时信号和信号量是不同的!
信号量多用于进程间的同步与互斥,简单的说一下同步和互斥的意思
同步:处理竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的个先后执行顺序
互斥:互斥访问不可共享的临界资源,同时会引发两个新的控制问题(互斥可以说是特殊的同步)
竞争:当并发进程竞争使用同一个资源的时候,我们就称为竞争进程
简单说一下信号量的工作机制(因为真的很简单),可以直接理解成计数器(当然其实加锁的时候肯定不能这么简单,不只只是信号量了),信号量会有初值(>0),每当有进程申请使用信号量,通过一个P操作来对信号量进行-1操作,当计数器减到0的时候就说明没有资源了,其他进程要想访问就必须等待(具体怎么等还有说法,比如忙等待或者睡眠),当该进程执行完这段工作(我们称之为临界区)之后,就会执行V操作来对信号量进行+1操作。
临界区:不是个简单的区域!是加锁区间的代码!
临界资源:只能被一个进程同时使用(不可以多个进程共享),要用到互斥
我们可以说信号量也是进程间通信的一种方式,比如互斥锁的简单实现就是信号量,一个进程使用互斥锁,并通知(通信)其他想要该互斥锁的进程,阻止他们的访问和使用(老子正在用!@_@!)
其实信号量的操作函数和之前几个都是类似的,都是套路
[b]信号量的创建[/b]
一定会觉得熟悉的!和消息队列十分类似
函数原型:int semget(key_t key, int nsems, int semflg);
头文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
参数解析
key_t这个就不详细解释了,在消息队列篇就讲过了
nsems这个参数是指创造出几个信号量,准确的来说,semget这个函数创建出来一个信号量集(就是包含好多信号量的那种,可以简单的理解为信号量数组),这就是说创建有几个信号量的信号量集,可以选择只创建一个
semflg还是一样的,用到两个参数就够了IPC_CREAT 和 IPC_EXCL怎么用的话我在消息队列篇讲过了,就不多费口舌了
下面会看到信号量的一个缺点
[b]信号量的初始化[/b]
这说明信号量的初始化和创建是分开操作的,为什么是分开的,因为创建的时候没有给信号量的大小的参数,信号量是可以设置大的(不是只可以设置为1),当然设置为1就是互斥访问了,比如典型的生产者和消费者问题,但是如果像是读者写者中的读者信号量可不一样(因为可以有多个读者同时读),这里就不解释这个模型问题了。
缺点就是,一旦初始化和创建分开之后,就会有线程安全的问题,进程A刚创建一信号量还没有初始化,进程B便已经开始对该信号量进程P操作(哥哥!我还没赋值初始化!)根本无法进行-1操作,会阻塞的,我都不知道咋回事(亲身体验该错误)。。。
废话不多说了,函数还是套路
函数原型:int semctl(int semid, int semnum, int cmd, ...);
头文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
参数解析
第一个semid就不多说了标识信号量用的,semget成功时候的返回值就是它
semnum这个就很有用了,刚才说了申请的是一个信号量集,这个参数就是告诉函数要初始化的是第几个信号量,该参数是从0开始的,0表示第一个信号量
cmd这次用这个SETVAL表示设置变量
当SETVAL被设置之后,可变参数列表的第四个参数就可以用上了,这里传入一个神奇的联合体(我在手册里找到了,但是死活用不了它,还是自己写了一个)
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) */ };
就是它!就是它!就看第一个吧,其他我也没用了(英语我也看不太懂),val就是设置的初值啦,传入的时候直接传入就行了,参数就是一个变量(不用传指针)
这个函数一会删除我们还会遇到它,ctl就是control控制的简写,很多操作都有它
[b]信号量操作[/b]
一开始就写了操作的两种方式,一种叫P操作,一种叫V操作,都是通过一个函数实现的,这个函数还关联一个结构体
函数原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
头文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
参数解析
第一个semid老朋友了
第二个就是那个结构体了,贴出来看
第三个表示你想同时操作几个信号量(为什么不是表示index of信号量呢,在结构体里有一起说),因为信号量集可能有很多信号量,每次一个一个操作不现实,这个参数就是提供一个同时操作的
struct sembuf { unsigned short sem_num; /* semaphore number */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ };
sem_num就是index of信号量,你想操作的信号量的下标,sem_op是个短整型,这里给1或者-1分别表示V和P操作,而最后一个semflg手册中提到了两个
IPC_NOWAIT:一看就是IPC通用的,非等待,不让忙等待只好去休眠(或者别的,反正不能循环等待)
SEM_UNDO:这个一看就是信号量独有的,看英文也能看出来UNDO是取消之前做过的,什么意思呢,不得不提到,当进程在临界区工作(持有锁)的时候,是不允许挂(挂掉更不好啦)起的!!!这很重要(又要加篇幅啦!@_@),因为一旦挂起或者休眠,有可能就醒不了啦!(挂起有可能等待事件)这样不好,会让等待的其他进程饥饿,尤其是挂掉的时候,肯定就更糟糕了,根本就无法执行V操作,等待的进程就会无限的等待下去,递归申请信号量也是不好的,尤其是互斥信号量的时候会造成死锁。这时候UNDO就出来把之前做过的东西都取消掉,P操作就当没执行过,信号量又从0变回了1,皆大欢喜。
[b]信号量的删除[/b]
semctl又来了,和消息队列的删除类似
函数原型:刚才刚写了~!
头文件:刚才刚写了~!
参数解析:刚才刚写了~!
不一样的只是把SETVAL为换成IPC_RMID就行了(当然后面的可变参数列表也不要了,可以看一下测试的例子)
事已至此,基本操作就说完了,废话少说,show me the code
我的程序分为comm.h(公共头文件) comm.c(封装基本函数) server.c(采用fork完成父子进程间使用信号量) 一共3个文件
基本功能:模拟线程安全(简单版本),父进程会打印"A""A"子进程会打印"B""B",因为执行顺序的原因可能会交叉打印,但我不要这样,我要让AA和BB分别连着!完成这个功能
先看之前的版本
交叉打印不是我想要的
功能完成连续打印
[b]comm.h[/b]
#include <stdio.h> #include <sys/ipc.h> #include <string.h> #include <unistd.h> #include <sys//sem.h> #include <stdlib.h> #include <errno.h> #define _PATH_NAME_ "/tmp" #define _PROJ_ID_ 0x6666 static int comm_create_sem(int flags,int num); int create_sem(int num); int get_sem(); void P_sem(int sem_id,int index); void V_sem(int sem_id,int index);
[b]comm.c[/b]
#include "comm.h" 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) */ }; static int comm_create_sem(int flags,int num) { key_t _key=ftok(_PATH_NAME_,_PROJ_ID_); if(_key<0) { printf("%d:%s",errno,strerror(errno)); } int sem_id; if((sem_id=semget(_key,num,flags))<0) { printf("semget errno,%d:%s",errno,strerror(errno)); } return sem_id; } int create_sem(int num) { int flags=IPC_CREAT | IPC_EXCL; int sem_id=comm_create_sem(flags,num); union semun v; v.val=1; if(semctl(sem_id,0,SETVAL,v)<0)//init { printf("init error,%d:%s",errno,strerror(errno)); } return sem_id; } int get_sem() { int flags=0; return comm_create_sem(flags,0); } void P_sem(int sem_id,int index) { struct sembuf s; s.sem_num=index; s.sem_op=-1; s.sem_flg=0; if(semop(sem_id,&s,1)<0) { printf("op errro,%d:%s",errno,strerror(errno)); } } void V_sem(int sem_id,int index) { struct sembuf s; s.sem_num=index; s.sem_op=1; s.sem_flg=0; if(semop(sem_id,&s,1)<0) { printf("op error,%d:%s",errno,strerror(errno)); } } void destory_sem(int sem_id) { semctl(sem_id,0,IPC_RMID); }
[b]server.c[/b]
#include "comm.h" int main() { int pid; pid=fork(); if(pid>0) { //father int sem_id=create_sem(1); while(1) { P_sem(sem_id,0); printf("A"); fflush(stdout); sleep(1); printf("A"); fflush(stdout); V_sem(sem_id,0); } } else { //child while(1) { int sem_id=get_sem(); P_sem(sem_id,0); printf("B"); fflush(stdout); sleep(1); printf("B"); fflush(stdout); V_sem(sem_id,0); } } return 0; }
相关文章推荐
- iOS设计模式(七) 迭代器模式
- A^B约数之和
- RedHat 7.2 KVM通过V2V迁移VMware的虚拟机
- Codeforces Round #361 (Div. 2) B BFS最短路
- 报告感想
- 华为HCIE理论与实操笔记【2-VRP BootRom】
- redis 实战系列(一)
- RedHat 7.2 KVM通过V2V迁移VMware的虚拟机 推荐
- java调用webservice
- 面试题(单例模式两种写法)
- POJ 2369 Permutations
- Android 什么是AIDL?
- OC 实现5升6升桶装3L水,通过定时器分步实现
- java学习路程
- JavaMail发送邮件AuthenticationFailedException异常解决
- POJ 3069
- JS中的prototype属性实现继承
- Codeforces Round #361 (Div. 2) B. Mike and Shortcuts
- ios developer tiny share-20160706
- ios developer tiny share-20160706