进程通讯(三)--信号量

18 篇文章 0 订阅
17 篇文章 0 订阅
 信号量虽然也是进程通讯的一部分,但比起其他的通讯方式比如管道,消息队列这些直接发送数据的形式不太相同。主要起到控制 同步异步 的作用。
  何为同步异步?听到同步不要因为这个“同”字就联想到俩进程同时运行。恰恰相反,同步往往要求一个进程等待另一个进程而达到协同作用。比如AB俩进程,A进程执行到了某段代码需要   B进程运行返回的数据 ,那么A就会等到了B进程执行直到B返回了A需要的数据时,A才会继续执行。这俩进程的关系是合作的,互相需要的,是同一条船上的,同一个步伐的,所以叫同步。
  异步就是AB进程自己执行自己的,他俩之间可能会存在交流或者通讯,但不是必需的,谁没了谁也能照样运行下去。
   


    我们之前说的管道,是一个进程A给里面写,另一个进程B从里面读。
   可以说数据长啥样完全由A里决定,B读到啥就是啥。那有人肯定会说这不是废话吗。先别急着骂我,我们的信号量就不是这样的。
  信号量相当于个什么东西呢。相当个高级锁,锁的概念以前有说过。
这几篇博客
   打个比方吧,就好像有两个房子,房子中间有个管道连在一起。
   有名无名管道,消息队列就好比一个房子的人给另一个房子的人传了一封信,接受消息的进程 接受到的消息内容跟自己代码中的一毛钱关系没有,比如打印接受到的字符串,谁能想到打印的内容居然是某个进程发来“helloworld”?   
而信号量好比一个房子的人对着管道里喊,你可以看信了,而另一个房子里的人收到喊声后才会打开自己房子里本来就有的信封去看内容,即接受了特定批准后,执行自己代码中早已准备好的部分,而那个部分平时不能乱执行,只有在需求到的时候才能执行,运行的代码完全是自己实现准备好的。
自己早准备了一份helloworld字符串要打印,只是存在打印和不打印的情况,打印的内容肯定是helloworld,不可能是别的什么东西。
丑陋的示意图

常规通讯,例如管道等

信号量


那有人会说,那么这不算通讯吧,既然信本来就在自己的房子里何必又去等待指令后才去看呢?就算看了也是自己本来都提前写好的东西,没有意义吧....

其实这的确算是通讯,举个例子,B进程功能是输出 “yes sir!”的,但必须接受到A的命令才会打印。
A只有在触发某些条件才会给B发送命令,比如检测到输入为“order B”,才会让信号量的值改变。B才会因为信号量值的增加而 执行那个yes sir。
这个过程下来不就是个简单的通讯过程吗?虽然B没有接受到任何消息去打印,它只会打印自己代码中早已经写好的yes sir,但B进程能因为A进程的检测做出反应,这不就是通讯吗?难道还是特异功能隔空感应啊?
其实信号量往往跟共享内存结合一起,共享内存就是多个进程同用一个内存,那么信号量的左右不是凸显出来了吗。需要的时候更改自己的数据,防止临界资源数据竞争,而共享内存就相当于通讯的进程们处理自己的东西。但是处理自己的东西时要有条有理,不可互相抢占覆盖,信号量刚好起到了制约协同的功能。

 扯了这么多,还是来看看信号量的具体实现吧。
  信号量的原理就是个计数器。能被通讯之间进程共同看到的计数器,只有要计数器不为0的时候,获取信号量才能成功,获取成功后就会让计数器减少一个,这称之为p操作。
获取信号量执行完自己该做的事情后,就要把信号量返还回去,计数器就会加1,这称之为v操作。

比如信号量初始只有一个,一个进程p操作了,然后信号量就变为0了,其他进程想p就不行了,只能等待到信号量不为0时才可以,信号量不为0只能在第一个进程执行完它的任务后使用v操作才能变为1,这时其他进程才可能使用p操作继续运行。而其他没有抢到那次p操作的,只能等刚抢到p的第二个进程运行一部分使用v归还计数器了才可能抢到p去停止等待。。。。。

刚只是限制了同时1个进程获得信号量继续运行。如果想要设置3个进程呢。信号量设置为3就好了,所以说信号量很灵活,锁就相当于值为1的信号量。而信号量本身可以随意设置数字,比起锁来说就更强大了。

实现的函数如下

int semget((key_t)key,int nsems,int flag); //创建或获取

第一个参数key就是自己随便写的数字,当作是自己代码中的标记

第二个是信号量个数,它一般为1,不要与信号量多少混淆,它代表一次创建几个信号量,不要和信号量里面具体设置的值分不清。简单而言就是我们之前说的影响pv操作的是每个信号量的信号量多少,好比停车场里是否有车位。车位不为0就可以停车(p操作)。而这个函数的第二个参数好比是停车场的个数,一般都是一个停车场。

第三个参数就是好比创建文件函数中的那个 0664|0_CREAT一样,不过这里要换成IPC的IPC_CREAT

最后返回值就是标识符,好比文件函数中的fd,为了后面一系列函数使用的一号参数


int  semop(int semid,struct sembuf  *buf,int lenth);  //改变信号量的值

这个函数就是用来改变信号量的值。p v操作都需要它,第二个参数就是个结构体指针,其中sem_op成员就是控制具体让信号量加1还是减1,加1就设置为1,减1就设为-1

返回代表是否成功  -1表示失败

int semctl(int semid,int pos,int cmd,);//信号量控制函数

解释下第二个参数,常用的是IPC_RMID或IPC_SETVAL, 前者故名思意是删除这个信号量,如果删除可以忽略第三个参数。

后者是创建信号量后初始化用的,初始成为什么呢?初始成第三个参数联合体里的值。联合体一般有三个成员,不过目前我们设置1个就好,设置个val成员。就是每个信号量的初始值,停车场里初始车位是多少。如果为1,1个车位,就代表可能进程们之间要抢这个车位了,竞争关系;如果为0,就代表进程们直接要等待某个进程使用v操作开车走人了,才能执行p操作之后的内容,属于一个进程为其他进程服务,或者说是前后关系。

最后返回值也是表示是否成功  习以为常的-1失败


然而可以看出函数稍微有点复杂和乱,我们实际需要的功能需要封装一下。

比如获得信号量,我要先检查信号量是否已经存在了,如果存在就直接用semget,如果不存在,我在semget创建后还要使用semctl进行初始化。

v  p操作也要先分别设置联合体的为1和-1,然后再使用semop函数改变信号量多少。

以下封装函数代码比起上课时多了些参数,在使用时也更灵活些。

头文件sem.h



sem.c



测试个小例子,input程序负责输入单词,直到输入ok了,B进程打印100内素数(干啥都是,不打印素数或者打印helloworld也可以,要的是输入ok后的给个反应)。

代码:




结果:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值