热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深度:PHP进程间通信探究

PHP进程探究PHP作为解释器运行通过线程或者进程都能实现(如果使用Apache,那么就可能使用多线程模型。使用php-fpm,就是使用多进程模型,这里以多进程模型解释)。

PHP进程探究

PHP作为解释器运行通过线程或者进程都能实现(如果使用Apache,那么就可能使用多线程模型。使用php-fpm,就是使用多进程模型,这里以多进程模型解释)。服务器每接收到一个请求就要起一个PHP进程,平均一个PHP进程消耗内存2M左右(默认最大为8M,参数可以设置)。独立的进程让PHP能专一的做自己的解释工作,程序员也从复杂的代码逻辑中走出来,不用担心资源的竞争和各种锁问题。独立进程虽好但这也导致想通过多进程或者异步来提速成本非常的高(主要是开发难度)。如果一定要通过PHP实现多进程和异步其实是很容易做到的。

PHP有很多第三方扩展,比如Swoole能让PHP像Node一样实现异步。PHP官方扩展库pcntl_*能很简单的实现多进程。扩展虽好,但实际应用时切忌要慎重,便利的同时风险也来了。比如对多进程的控制,处理不好很容易导致程序 死锁 ,CPU内存爆表、服务器宕机。异步回调的Coding方式与PHP本身的编程思想有一定出入,驾驭不好也是灾难。

当然也不能说的太吓人,在实际的项目中我们有很多场景不得不考虑通过多进程或者异步来优化程序。这里举一个很常见的例子『发送消息通知』,比如短信和邮件。这里说一个实际的场景:企业需要给200W用户发短信通知,短信接口支持最大100次/秒的调用频率,短信接口每次调用耗时300毫秒。如果单进程跑脚本的话,需要7天才能把短信发完。如果我们起30个进程,每秒能发送100条短信,6个小时内能发完,能提速30倍。优化方案确定之后,我们再看如何通过PHP去实现这样一个脚本。

一. pcntl扩展初探;

  1. 通过pcntl扩展创建多进程,参见如下代码;

    function demo(array $phoneList){

    $cnt = count($phoneList); //测试数组大小

    $slice = 30; //需要调用的进程数量

    $master = array_chunk($phoneList,floor($cnt/$slice));

    $childList = []; while($slice >= 0)

    {

    $pid = pcntl_fork(); if($pid > 0){

    $childList[$pid] = 1; //$pid>0表示当前还在执行父进程的代码

    //这里最好啥都不做,每次执行pcntl_fork都会执行这里的代码。

    //这里的代码执行完之后 会将$pid设置为0,然后jump到pcntl_fork代码之后,重新做判断;

    }elseif($pid == 0){ //这里写我们的逻辑

    foreach($master[$slice] as $val)

    { //这里发生短信

    echo sprintf(“%s Child:%s \r\n”,$slice,$val);

    } //子进程执行完之后务必需要关闭;

    exit();

    }else

    { //程序发生错误也需要关闭程序

    exit();

    }

    $slice–;

    } // 等待所有子进程结束后回收资源

    while(!empty($childList)){

    $childPid = pcntl_wait($status); if ($childPid > 0){ unset($childList[$childPid]);

    }

    }

    }/** 运行的结果如下,phone不是连续的

    Slice id:19,phone:66558

    Slice id:23,phone:79921

    Slice id:19,phone:66559

    Slice id:23,phone:79922

    Slice id:19,phone:66560

    Slice id:23,phone:79923

    Slice id:19,phone:66561

    Slice id:23,phone:79924

    Slice id:19,phone:66562

    Slice id:23,phone:79925

    **/

    通过pcntl扩展,几句代码就使用多进程将发消息通知的功能提速了30倍。不过这么简单的多进程编码,我为什么会在文章开始形容的如此复杂呢?

    重点和难点还是 进程间通信 ,因为我们给用户发短信的每个子进程是相对独立的,进程之间没有通信,不会互相传递数据状态。所以不会发生资源抢占与锁问题。假如需求发生变化,我们需要按用户的活跃度高低给用户发短信,该怎么做?

    通俗点解释如下:一个盘子里有30个苹果,需要发给30个人,由3个人负责发苹果。最简单的办法就是我们先把苹果分成3份,3个人一人一份,很快就能发完。但是如果我们要按照苹果的大小顺序去发,把大苹果先发出去,此时我们就没办法分成3份了,只能三个人互相去挣当前最大的,很容易就打起来。那该怎么做呢?最常见的办法就是使用一个工具把所有苹果按由大到下的顺序放在里面,每次只能取一个,这样就解决了资源抢占的问题。

    关于进程间资源抢占的问题非常的复杂,编码难度非常高,这也是为什么很少使用PHP跑多进程的原因。当需要用到多进程时我们更愿意去使用Python或者 Java ,它们对多线程封装的更好。需要重点说的是PHP并不是不能写多进程的程序,也不是像其他人说的不稳定,而是编码费时,维护成本高。

二. 进程间通信

常见的进程通信方式有:消息队列、共享内存与 信号量 、管道、socket,我将一一举例说明。

  1. 消息队列

    『消息队列』是在消息的传输过程中保存消息的容器。消息队列管理器相当于消息发送者和接收者的中介。消息队列的主要目的是创建路由并且保证消息可靠传递;如果发送消息时接收者不可用,消息队列会保留消息,直到有人接收它。

    消息队列可提供临时存储的功能并且能保证消息可靠的传递,我们正好使用它实现进程间通信。当然消息队列不单单用于进程间通信,他的应用领域非常广。比如消息队列非常适用于解决消费者和生产者的问题,因为生产者和消费者之间总会存在『速度差』。比如生产者突然少了10个,两边处理的速度就会不平衡,会导致排队阻塞,服务不可用。这肯定不是我们想看到的,如果这时候引入消息队列将两个系统解耦,无论谁慢了都不会影响整体业务。

    function demo(array $phoneList){ global $msgQueue;

    $cnt = count($phoneList); //测试数组大小

    $slice = 3; //需要调用的进程数量

    $childList = []; //主进程先发送一条消息,告诉子进程可以发送第一条短信了

    msg_send($msgQueue,MSG_TYPE,0); while($slice >= 0)

    {

    $pid = pcntl_fork(); if($pid > 0){

    $childList[$pid] = 1; //父进程什么都不用做

    }elseif($pid == 0){ //子进程不停的请求,直到所有短信发送完成

    while(msg_receive($msgQueue,MSG_TYPE,$msgType,1024,$ message ))

    { if($cnt>intval($message))

    {

    printf(“Slice id:%s,phone:%s \r\n”,$slice,$phoneList[$message]);

    $message = $message + 1;

    msg_send($msgQueue,MSG_TYPE,$message);

    }else

    { //通知其他进程一切都结束了

    msg_send($msgQueue,MSG_TYPE,$cnt); exit();

    }

    }

    }else

    { //程序发生错误也需要关闭程序

    exit();

    }

    $slice–;

    } // 等待所有子进程结束后回收资源

    while(!empty($childList)){

    $childPid = pcntl_wait($status); if ($childPid > 0){ unset($childList[$childPid]);

    }

    }

    }const MSG_TYPE = 1;//创建消息队列$id = ftok(__FILE__,’m’);

    $msgQueue = msg_get_queue($id);

    demo(range(0,900));/**运行结果,按大小输出

    Slice id:1,phone:895

    Slice id:1,phone:896

    Slice id:2,phone:897

    Slice id:3,phone:898

    Slice id:3,phone:899

    **/

  2. 共享内存与信号量

    『共享内存』很容易理解,就是在内存中找一块区域,所有进程都能读写。『信号量』是系统提供的一种原子操作,进程在开启信号和结束信号之间拥有共享内存的『绝对占有』权,这样能有效的防止多个进程读取同一个资源时发生死锁。

    function demo(array $phoneList){ global $shareMemory; global $signal;

    $cnt = count($phoneList); //测试数组大小

    $slice = 3; //需要调用的进程数量

    $childList = []; while($slice >= 0)

    {

    $pid = pcntl_fork(); if($pid > 0){

    $childList[$pid] = 1; //父进程什么都不用做

    }elseif($pid == 0){ while(true)

    { // 标记信号量,这里被我承包了

    sem_acquire($signal); //检测共享内存是否存在

    if (shm_has_var($shareMemory,SHARE_KEY)){ //从共享内存中拿数据

    $val = shm_get_var($shareMemory,SHARE_KEY); if($val>=$cnt)

    {

    sem_release($signal); break;

    }else

    {

    printf(“Slice id:%s,phone:%s \r\n”,$slice,$phoneList[$val]);

    $val ++; //再将数据写入共享内存

    shm_put_var($shareMemory,SHARE_KEY,$val);

    }

    }else{ // 无值会,先初始化

    shm_put_var($shareMemory,SHARE_KEY,0);

    } // 用完释放

    sem_release($signal);

    } exit();

    }else

    { //程序发生错误也需要关闭程序

    exit();

    }

    $slice–;

    } // 等待所有子进程结束后回收资源

    while(!empty($childList)){

    $childPid = pcntl_wait($status); if ($childPid > 0){ unset($childList[$childPid]);

    }

    }

    }const SHARE_KEY = 1;// 创建一块共享内存$shm_id = ftok(__FILE__,’a’);

    $shareMemory = shm_attach($shm_id);// 创建一个信号量$sem_id = ftok(__FILE__,’b’);

    $signal = sem_get($sem_id);

    demo(range(0,900));// 释放共享内存与信号量shm_remove($shareMemory);

    sem_remove($signal);/**运行结果,按大小输出

    Slice id:1,phone:775

    Slice id:3,phone:776

    Slice id:3,phone:777

    Slice id:3,phone:778

    Slice id:0,phone:779

    Slice id:0,phone:780

    **/

  3. 管道

    管道是比较常用的进程间通信手段,管道又分为匿名管道(pipe)与具名管道(mkfifo),匿名管道只能用于具有亲缘关系的进程间通信,而具名管道可以用于同一主机上任意进程。

    pipe与mkfifo的主要差别是mkfifo会创建一个特殊的FIFO物理文件,这个FIFO文件其他进程都可以像读写一般文件一样读写。再写下去文章就太长了,之后写下一篇吧。

    未完待续……

PS:所有代码都放到了GitHub:php_thread_demo

沟通和互动以及更多干货,欢迎关注新浪微博:@阿里云云栖社区


推荐阅读
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • 006_Redis的List数据类型
    1.List类型是一个链表结构的集合,主要功能有push,pop,获取元素等。List类型是一个双端链表的结构,我们可以通过相关操作进行集合的头部或者尾部添加删除元素,List的设 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • 服务器上的操作系统有哪些,如何选择适合的操作系统?
    本文介绍了服务器上常见的操作系统,包括系统盘镜像、数据盘镜像和整机镜像的数量。同时,还介绍了共享镜像的限制和使用方法。此外,还提供了关于华为云服务的帮助中心,其中包括产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题和视频帮助等技术文档。对于裸金属服务器的远程登录,本文介绍了使用密钥对登录的方法,并提供了部分操作系统配置示例。最后,还提到了SUSE云耀云服务器的特点和快速搭建方法。 ... [详细]
author-avatar
weiwei
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有