本文介绍的Perl进程间数据共享内容主体来自于《Pro Perl》的第21章。
IPC简介
通过fork创建多个子进程时,进程间的数据共享是个大问题,要么建立一个进程间通信的通道,要么找到一个两进程都引用的共享变量。本文将介绍Unix IPC的近亲System V IPC:message queues(消息队列)、semaphores(信号量)和shared memory-segments(共享内存段)。它们都是IPC结构,它们被非常广泛地应用于进程间通信。它们的帮助文档可参见:
$ perldoc IPC::Msg
$ perldoc IPC::Semaphore
$ perldoc IPC::SharedMem
但是,并非所有操作系统都支持System V IPC,对于那些不遵守POSIX规范的平台就不支持。当然,也并非一定要在Unix操作系统上才能使用IPC,只要操作系统支持IPC就可以,而且就算是Unix系统上也并非一定支持IPC,可以使用ipcs
命令来查看是否支持:
$ ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
message queues、semaphores和shared memory segments的共同点在于它们的数据都持久存在于内存中,且只要知道资源的ID且有权限访问,就可以被任意一个进程(所以随意多个进程)访问到。由于数据在内存中持久,且数据资源进行了ID标识,这可以使得程序退出前保存状态,然后重启时再次获取到原来的状态。
严格地说,Perl支持的IPC在于可以调用一些IPC函数:msgctl、msgget、msgrcv、msgsnd、semctl、semget、semop、shmctl、shmget、shmread、shmwrite。它们几乎是对C对应函数的封装,非常底层。尽管这些函数的文档非常丰富,但这些函数并不容易使用,IPC::
家族的模块提供了面向对象的IPC功能支持。
IPC::SysV模块
要使用IPC的一些函数,常常需要导入一些IPC::SysV
模块中的常量,因此很多使用IPC的程序中,都会:
use IPC::SysV:
它里面定义了很多常量,完整的可参见perldoc -m IPC::SysV
,下面是一些常见的常量:
Message Queue(消息队列)
曾经消息队列是进程间通信的唯一有效方式,它就像管道一样,一端写入一端读取。对于消息队列而言,我们写入和读取的数据都称之为消息(message)。
可以创建两种类型的消息队列:私有的(private)和公有的(public)
- 私有队列只对创建它的进程和它的子进程可以访问,当然还可以通过权限控制的方式来改变访问权限
- 公有队列只有有权限,且知道资源ID的进程可以访问
例如,创建一个私有的消息队列。
use IPC::SysV qw(IPC_PRIVATE IPC_CREAT S_IRWXU);
use IPC::Msg;
my $queue = IPC::Msg->new IPC_PRIVATE S_IRWXU | IPC_CREAT;
IPC_Msg
的构造函数new有两个参数,第一个是要创建的消息队列的资源ID,在IPC SysV中常称为KEY,对于私有队列来说,KEY需要指定为IPC_PRIVATE
,第二个参数是访问该队列的权限,S_IRWXU
表示队列的所有者(U)可以对该队列进行读(R)、写(W)、执行(X)操作。此处还配合了IPC_CREAT
,表示如果队列不存在就创建新队列。
权限部分也可以写成数值格式的:
my $queue = IPC::Msg->new IPC_PRIVATE 0700 | IPC_CREAT;
所以,这里创建的私有队列只有创建者进程和子进程可以执行读写执行的操作。
如果想要创建一个公有队列,需要为该公有队列提供一个资源ID,资源ID是一个数值。下例中给的资源ID是10023,权限是0722,表示创建队列的进程拥有读写执行操作,而资源所在组或其它用户进程只能写队列。
my $q = IPC::Msg->new 10023, 0722 | IPC_CREAT;
如果其它进程想要访问这个公有队列,只需通过new方法指定这个公有队列的KEY即可即表示构建这个已有的队列,不要指定IPC_CREAT
修饰符,否则表示创建动作(尽管IPC结构存在时不会创建,但他代表了创建这个动作,而非访问动作)。如果要获取的公有队列不存在,则返回undef。如下:
my $q = IPC::Msg->new 10023, 0200;
而对于私有队列,想要知道它的KEY,可以使用id()方法:
$KEY = $queue->id
发送和接收消息队列
有了消息队列的对象结构之后,就可以操作这个消息队列,比如发送消息,接收消息等。相关文档参见man msgsnd
。
向队列发送消息和从队列中接收消息的方式为:
$queue->snd($type, $wr_msg, [ $flags ]);
$queue->rcv(\$rd_msg, $length, $type, [ $flags ]);
$wr_msg
为想要发送的消息$rd_msg
是从消息队列中读取消息保存到哪个标量变量中$type
是一个正整数,表示消息队列的类型,可在rcv
方法中指定这个正整数表示选择接收哪个数值类型的消息$length
表示消息队列中最多允许接收多少条消息。如果消息长度大于该值,则rcv返回undef,并且$!
设置为E2BIG$flags
是可选的,如果设置为IPC_NOWAIT
,则这两个方法不会阻塞,而是立即返回($!
设置为EAGAIN
)
关于rcv type和flag的规则,参考如下解释。
rcv Type的解释:
整数值 意义
-----------------------------
0 rcv总是读取队列的第一条消息,无视type
>0 rcv总是读取该类型的第一条消息。例如,
type=2,则只读取type=2的消息,如果不存在,
则一直阻塞直到有type=2的消息。但是可以设
置IPC_NOWAIT和MSG_EXCEPT常量改变这种模式
<0 rcv读取类型不大于type绝对值(从小到大)的第
一条消息。不严谨,但可看示例描述:如果rcv的
type=-2,则首先读取type=0的第一条消息,如
果不存在type=0的消息,则继续读取type=1的第
一条消息,不存在则继续读取type=2的第一条消息
flag的解释:
flag值 意义
------------------------------
MSG_EXCEPT rcv读取第一条非type值的消息。例如,rcv
的type=1,则读取第一条type不为1的消息
MSG_NOERROR 允许消息过长超过$length时截断超出的部分,
而不是在这种情况下返回E2BIG错误
IPC_NOWAIT rcv在请求的消息类型不存在时不要阻塞等待,
而是立即返回,且设置$!的值为EAGAIN
将上面的解释合并起来,很明确的意思是我们可以通过设置不同的type来实现多级通信的消息队列,这一切都交给我们自己来决定,例如对不同子进程或线程发送不同的消息。
获取和设置消息队列的属性
可以使用set
方法来修改消息队列的权限,它需要一个key-value格式的参数。
例如:
$queue->set(
uid => $user_id, # chown
gid => $group_id, # chgrp
mode => $perm, # 8进制权限位或S_格式的权限
qbytes => $queue_size, # 队列最大容量(capacity)
);
另外,可以使用stat
方法获取队列的属性对象,通过这个属性对象,可以直接修改队列的对应属性。只是需要注意的是,当通过stat对象更改属性时,不会立即应用到消息队列上生效,只有通过set方法设置后,设置才会立即生效。
my $stat = $queue->stat;
$stat->mode(0722);
$queue->set($stat);
最后,如果拥有队列的执行权限,可以通过remove
方法销毁这个队列:
$queue->remove;
如果无法删除队列,则remove返回undef,并设置$!
。其实删除队列挺重要的,因为如果程序退出,队列可能会继续保留在内存中(前文已经说过了,IPC对象都是持久化在内存中的)。
信号量和共享内存
待续
原文地址:https://www.cnblogs.com/f-ck-need-u/p/10404275.html