书接上回:
第15章 进程间的同步和通信
15.1 概览
-semaphores
-mailboxes
-named events
15.2 综述
简单的进程间通信可以通过named events来实现,有event trigger和event control过程,分别需要依赖符号->和@,但是named events只能实现静态对象的同步通信,更复杂的进程间通信需要一些机制来实现,比如旗语机制和mailbox机制。
旗语和mailbox是内建类,已经放在了std package里,因此可以直接使用。
15.3旗语
旗语的使用:只有先拿到旗语的key,进程才能够继续往下执行,当key不够时,其他进程处于等待状态,直到key被归还回来。
旗语的声明:
旗语的创建:
获取旗语的key:
归还旗语的key:
没有阻塞获取旗语的key:
15.3.1 new()
函数原型:
作用:创建指定数量key的旗语对象,默认key数量为0。
15.3.2 put()
函数原型:
作用:归还key,默认归还数量是1。
15.3.3 get()
函数原型:
作用:获取key,默认获取数量是1。
15.3.4 try_get()
函数原型:
作用:无阻塞获取key,默认获取数量是1。
15.4 mailbox
作用:实现进程间的通信
进程1可以把message放入mailbox,然后另一个进程去检索mailbox 里的meaasge,从而完成了进程间的通信。
Mailbox实际上就是由一个有边界或者无边界的queue组成。如果是有边界的mailbox,当mailbox里的queue满了时,想要继续存储meaasge到mailbox的进程就会被挂起,直到指定数量的空间被释放出来。无界queue组成的mailbox就不会出现进程被挂起的现象。
Mailbox声明:
Mailbox创建:
将message放入mailbox:
无阻塞将message放入mailbox:
获取mailbox里的message:
无阻塞获取mailbox里的message:
获取mailbox里message的数量:
15.4.1 new()
Bound参数为0时,表示创建一个无界queue的mailbox;否则,bound的值就是queue的size。
15.4.2 num()
获取mailbox里message的数量。
15.4.3 put()
往mailbox里放message。如果mailbox是满的,进程会被挂起直到空间被释放出来。存入顺序是FIFO的顺序。
15.4.4 try_put()
无阻塞的往mailbox里放message,因此这个函数只有对有界mailbox才有意义。如果mailbox没满,函数会返回一个正整数,如果满了,会返回0。存入顺序是FIFO的顺序。
15.4.5 get()
获取message。如果mailbox是空的,进程会被挂起直到有message;如果message类型和mailbox里message类型不一致,会报一个run-time error。
15.4.6 try_get()
无阻塞获取message。如果mailbox是空的,返回0,如果类型不匹配,返回一个负整数,如果mailbox不空且数据类型匹配,返回一个正整数。
15.4.7 peek()
Peek和get的区别是,前者是复制,后者是移出。如果mailbox是空的,进程会被挂起直到有message;如果message类型和mailbox里message类型不一致,会报一个run-time error。
15.4.8 try_peek()
无阻塞复制message。如果mailbox是空的,返回0,如果类型不匹配,返回一个负整数,如果mailbox不空且数据类型匹配,返回一个正整数。
15.4.9 参数化的mailbox
默认情况下,mailbox是无参数的,因此mailbox可以用来传递任何类型的message,一旦写入的message和获取message的类型不匹配,会在run-time阶段报error。
但是也可以创建一个参数化的mailbox,那么这个mailbox就只能传递指定参数类型的message,并且在编译阶段就会检查相关写入和获取message函数的参数类型,如果类型不匹配,就会报编译错误。
举例:
参数化mailbox和普通mailbox一样都有前面几节介绍的函数。
15.5 named events
当声明数据类型是event时,就表明这是一个named event。
Named event有触发和等待的动作,触发可以用符号->/->>实现;等待可以用符号@/wait()实现。
15.5.1 触发一个事件
语法:
触发的作用是使等待该事件的阻塞进程被解除。
->>是针对非阻塞事件的,作用是触发一个非阻塞事件的执行,可以加上delay控制。
15.5.2 等待一个事件
使用@符号等待一个事件被触发。
注意->语句要在@语句之后执行。否则@语句会一直被阻塞。
15.5.3内置的触发方法
方法原型:
如果在当前time step,事件被触发过了,那么调用该函数会返回1,否则返回0。如果事件为null,返回0。
这个方法一般是搭配wait()方法使用的,如下:
和前面的->/@组合不同的是,wait()/trigged()方法无需强制规定wait()语句必须出现在triggered()语句之前,它也可以和trigger()语句在同一个timestep执行,当wait()和triggered()同时发生时,wait()阻塞进程始终会被解除。
举例:
上面第一个fork里,@语句在trigger()语句之前执行,这是保证@语句阻塞的前提条件,所以能够跳出fork;第二个fork,->和wait是同时刻执行,也能跳出fork。
15.5.4 事件排序:wait_order()
wait_order构造挂起调用进程,直到所有指定的事件都按照给定的顺序(从左到右)被触发,或者任何未被触发的事件被乱序触发,从而导致操作失败。
为了使wait_order成功,在序列中的任何一点,后续事件都应按规定的顺序触发,这些事件在此时都不应被触发,否则序列将已经失败。前面的事件并不局限于只发生一次。换句话说,一旦事件按照规定的顺序发生,就可以再次触发它,而不会导致构造失败。
只有列表中的第一个事件可以等待持久triggered事件。
当失败时所采取的操作取决于是否指定了可选的action_block else语句(失败语句)。如果指定了它,则在失败时执行该else语句。如果未指定fail语句,则失败将生成run-time错误。
举例1:
挂起当前进程,直到事件a、b和c按照a - > b - > c的顺序触发。如果事件触发的顺序乱了,则会产生run-time错误。
举例2:
在本例中,fail语句指定在失败时显示一条用户消息,但不生成错误。
15.5.5 named event可使用的操作符
事件可以使用赋值操作符,相当于将事件句柄传递给另一个事件,二者共享queue。
15.5.5.1 merging event
当使用赋值操作符时,相当于将两个事件进行了合并。使用->触发了任意一个事件都会导致两个阻塞进程被解除。举例:
15.5.5.2 回收event
当event被赋值为null时,任何对该事件的trigger都不起作用。举例:
15.5.5.3事件比较
只允许下面几种比较:
-和null或者其他事件使用==比较
-和null或者其他事件使用!=比较
-和null或者其他事件使用===比较
-和null或者其他事件使用!==比较