当前位置: 首页 > Linux

PHP多进程初探---关于进程间通信的二三事

时间:2023-04-06 06:44:06 Linux

【原文地址:https://blog.ti-node.com/blog...】常开多进程的目的进程就是要协同工作以加快效率,前面说过不同进程之间的内存空间是相互隔离的,也就是说进程A不能读写进程B中的任何数据内容,反之亦然。但是,有的时候,多进程之间必须要有一种相互通知的机制,用职场的话说就是“及时通信”。每个人都在一起做同一件事的不同部分,彼此“及时沟通”很重要。于是进程间通信诞生了,英文缩写IPC,全称InterProcessCommunication。常见的进程间通信方法包括:管道(未命名和命名)、消息队列、信号量、共享内存和套接字。最后一种方法今天就不说了,后面phpsocket编程中会讲到。前四种方式。流水线在*NIX上很常见。也是大家在使用linux的时候用到的。简单的理解就是|,比如ps-aux|grepphp,就是一个管道。大概是类似于ps进程和grep进程的意思吧。使用|来完成通讯。管道是半双工的(现在已经有系统支持全双工管道了),也就是说数据只能沿着管道的一个方向传输,不能在同一管道上反向传输数据。管道有两种,一种叫无名管道,一种叫命名管道。未命名管道只能在具有共同祖先的两个进程之间使用。简单的理解就是它们只能用于父进程和其他进程。子进程之间的通信,但命名管道可用于任何两个不相关进程之间的通信(这是我们稍后将演示的内容)。需要指出的是,消息队列、信号量和共享内存这三个IPC属于XSIIPC(XSI可以认为是POSIX标准的超集,简单粗略理解为C++到C)。这三个IPC在*NIX中一般有两个“名字”来命名,一个叫标识符,一个叫密钥。标识符是一个非负整数。每当一个IPC结构被创建然后被销毁时,标识符会被加+1到整数的最大整数值,然后从0开始重新计算。由于用于多进程通信,当多个进程使用XSIIPC时,它们需要用一个名字来找到对应的IPC,然后就可以读写了(这个术语叫做多个进程在同一个IPC结构上的聚合),所以POSIX的建议是,每当你创建一个IPC结构时,你应该指定一个与之关联的键(key)。一句话总结就是:identifier是XSIIPC的内部名称,key(key)是XSIIPC的外部名称。让多个进程收敛到XSIIPC上的方法大致有3种:使用指定的keyIPC_PRIVATE创建一个IPC结构体,然后将返回的标识符保存到文件中,然后读取标识符进行通信。使用通用头文件。这样做的缺点是IO操作比较多。将共同商定的密钥写入公共头文件。这样做的缺点是key可能已经关联了一个IPCi结构,因此在用这个key创建结构时可能会出错,然后必须删除现有的IPC结构并重新创建。确定文件路径名和项目ID,然后使用ftok将这两个参数转换为密钥。这将是我们使用它的方式。XSIIPC结构有一个对应的权限结构ipc_perm,它定义了IPC结构的创建者和拥有者。多进程通信之一:命名管道。在php中,创建管道的函数叫做posix_mkfifo()。管道创建后,其实就是一个文件,然后可以使用任何与读写文件相关的函数对其进行操作。代码大致演示一下:0){//在父进程中//打开命名管道,然后读取文本$file=fopen($pipe_file,"r");//注意这里fread会被阻塞$content=fread($file,1024);echo$content.PHP_EOL;//注意这里又被阻塞了,等待回收子进程,避免僵尸进程pcntl_wait($status);}运行结果如下:多进程通信2:消息队列。这个恐怕很多人都听说过,但印象往往停留在kafka、rabbitmq等服务器解耦网络消息队列的软件上。消息队列是消息的链表(一种常见的数据结构),但是这个消息队列是存放在系统内核中的(不是用户态)。一般我们的外部程序都是通过key来读写消息队列的。在PHP中,消息队列的操作是通过msg_*系列函数完成的。0){//在父进程中//使用msg_receive()函数获取消息msg_receive($queue,0,$msgtype,1024,$message);echo$message.PHP_EOL;//消息队列用完记得清理删除msg_remove_queue($queue);pcnlt_wait($status);}elseif(0==$pid){//在子进程中//向消息队列写入消息//使用msg_send()向消息队列写入消息。具体可以参考文档msg_send($queue,1,"helloword");exit;}运行结果如下:不过msg_send()和msg_receive()这两个函数值得进一步研究,这两个的每个参数都非常值得进一步研究和尝试。由于篇幅问题,这里不再详述。多进程通信之三:信号量和共享内存。共享内存是最快的进程间通信方式,因为不需要n个进程之间进行数据拷贝,而是直接操作相同的数据。其实信号量和共享内存是密不可分的,是一起使用的。有些关于*NIX的书籍甚至不建议新手轻易使用这种进程间通信的方式,因为这是一种极易产生死锁的方案。共享内存,顾名思义,就是内存中的一块区域,可以被多个进程读写。这里最大的问题就是数据同步的问题。例如,当一个进程更改数据时,另一个进程不能读取它,否则就会出现问题。因此,为了解决这个问题,引入了信号量。信号量是与共享内存结合使用的计数器。一般流程是这样的:当前进程获取要使用的共享内存的信号量。如果信号量大于0,表示可以使用这块共享资源,然后进程将信号量减1,如果信号量为0,则进程进入休眠状态,直到信号量大于0,进程从1唤醒。当一个进程不再使用当前共享资源时,它会将信号量减1。在这个地方,信号量的检测和减1是原子的,也就是说两个操作必须一起成功,这是由系统内核实现的。在php中,信号量和共享内存一共有这几个函数:其中sem_是信号量相关的函数,shm_是共享内存相关的函数。0){$child_pid[]=$pid;}}while(!empty($child_pid)){foreach($child_pidas$pid_key=>$pid_item){pcntl_waitpid($pid_item,$status,WNOHANG);取消设置($child_pid[$pid_key]);}}//休眠2秒,两个子进程都结束了sleep(2);echo'最终结果'.shm_get_var($shm_id,SHM_VAR).PHP_EOL;//记得删除共享内存数据,删除共享内存有个顺序,先remove再detach,反过来php可能会报错shm_remove($shm_id);shm_detach($shm_id);运行结果如下:准确的说,如果不使用sem,上面的运行结果一定概率下会产生1而不是2,但是只要加上sem,就必须100%保证是2,而其他值永远不会出现。【原文地址:https://blog.ti-node.com/blog...】