Perl进程间数据共享

本文介绍的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

时间: 2024-10-03 17:45:45

Perl进程间数据共享的相关文章

通过SharedPreferences实现进程间数据共享的问题详解

??之前为了解决应用的内存压力,在同一个应用中使用了多进程,但在程序自测的过程中发现不同进程之间的SharedPreferences数据不能共享,但应用内很多数据都是通过SharedPreferences来保存的,如果改成其它多进程通信的方式改动比较大.通过查看源码发现,在API Level>=11即Android 3.0可以通过Context.MODE_MULTI_PROCESS属性来实现SharedPreferences多进程共享,具体使用方式如下: SharedPreferences sp

Python之路(第三十九篇)管道、进程间数据共享Manager

一.管道 概念 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信. 先画一幅图帮助大家理解下管道的基本原理 现有2个进程A和B,他们都在内存中开辟了空间,那么我们在内存中再开辟一个空间C,作用是连接这两个进程的.对于进程来说内存空间是可以共享的(任何一个进程都可以使用内存,内存当中的空间是用地址来标记的,我们通过查找某一个地址就能找到这个内存)A进程可以不断的向C空间输送东西,B进程可以不断的从C空间读取东西,这

113 python程序中的进程操作-进程间数据共享(multiProcess.Manger)

一.进程之间的数据共享 展望未来,基于消息传递的并发编程是大势所趋 即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合,通过消息队列交换数据. 这样极大地减少了对使用锁定和其他同步手段的需求,还可以扩展到分布式系统中. 但进程间应该尽量避免通信,即便需要通信,也应该选择进程安全的工具来避免加锁带来的问题. 以后我们会尝试使用数据库来解决现在进程之间的数据共享问题. 1.1 Manager模块介绍 虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于

管道,进程间数据共享,进程池

一:管道   (了解) 使用:from multiprocessing import Process,Pipe 知识: 1 创建管道时候:Pipe()默认是双工的,如改成False,那么conn1只能接收,conn2只能发送. conn1,conn2=Pipe() 2 Pipe模块发送字符串不用bytes类型,直接是字符串类型. Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1, conn2表示管道两端的连接对象,强调一点:必须在产生Pr

Python并发编程-进程间数据共享

Manager中进程数据不安全 通过加锁解决 from multiprocessing import Manager,Process,Lock def main(dic,lock): lock.acquire() dic['count'] -=1 print(dic) lock.release() if __name__ == '__main__': m = Manager() l = Lock() dic = m.dict({'count':100}) #主进程中数据提供到子进程去操作 p_l

进程间的数据共享

# 以后我们会尝试使用数据库类解决现在进程之间的数据共享问题 # multiprocessing.Manager模块,是数据共享用的模块,共享的数据存在数据不安全问题,需要加锁操作 # from multiprocessing import Manager, Process # # # def process1(dic): # dic['count'] = 5 # print('子进程修改了dic') # # # if __name__ == '__main__': # m = Manager(

Python网编_进程间的数据共享

Manager提供了很多数据共享机制,但是对于一些基础数据类型来说,是数据不安全的,那么Q:如何解决呢? A: 需要我们自己手动加锁 from multiprocessing import Manager,Process,Lock # Process开子进程用,Lock数据加锁用,Manager进程间数据共享用 def work(d,lock): # lock.acquire() # d['count'] -= 1 # lock.release() with lock: # 上下问管理 注意了l

Windows进程间各种通信方式浅谈

1 Windows进程间通信的各种方法 进程是装入内存并准备执行的程序,每个进程都有私有的虚拟地址空间,由代码.数据以及它可利用的系统资源(如文件.管道等)组成. 多进程/多线程是Windows操作系统的一个基本特征.Microsoft Win32应用编程接口(Application Programming Interface, API) 提供了大量支持应用程序间数据共享和交换的机制,这些机制行使的活动称为进程间通信(InterProcess Communication, IPC), 进程通信就

进程间的通信方式比较(转载整理)

进程间的通信方式: 1.管道(pipe)及有名管道(named pipe): 管道可用于具有亲缘关系进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信. 2.信号(signal): 信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致得. 3.消息队列(message queue): 消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可