多进程应用大批量的数据是非常舒服的一件事情。

处理之前理解两个概念:孤儿进程和僵尸进程

孤儿进程:

是指父进程在fork出子进程后,自己先完了。这个问题很尴尬,因为子进程从此变得无依无靠、无家可归,变成了孤儿。用术语来表达就是,父进程在子进程结束之前提前退出,这些子进程将由init(进程ID为1)进程收养并完成对其各种数据状态的收集。init进程是Linux系统下的奇怪进程,这个进程是以普通用户权限运行但却具备超级权限的进程,简单地说,这个进程在Linux系统启动的时候做初始化工作,比如运行getty、比如会根据/etc/inittab中设置的运行等级初始化系统等等,当然了,还有一个作用就是如上所说的:收养孤儿进程。

僵尸进程:

是指父进程在fork出子进程,而后子进程在结束后,父进程并没有调用wait或者waitpid等完成对其清理善后工作,导致改子进程进程ID、文件描述符等依然保留在系统中,极大浪费了系统资源。所以,僵尸进程是对系统有危害的,而孤儿进程则相对来说没那么严重。在Linux系统中,我们可以通过ps -aux来查看进程,如果有[Z+]标记就是僵尸进程。

在PHP中:

1、pcntl_fork(),只管fork生产,不管产后护理,实际上这样并不符合主流价值观,而且,操作系统本身资源有限,这样无限生产不顾护理,操作系统也会吃不消的。

2、父进程对子进程的状态收集等是通过pcntl_wait()和pcntl_waitpid()等完成的。

演示并说明孤儿进程的出现,并演示孤儿进程被init进程收养:

  1. <?php
  2. $pid = pcntl_fork();
  3. if( $pid > 0 ){
  4. // 显示父进程的进程ID,这个函数可以是getmypid(),也可以用posix_getpid()
  5. echo "Father PID:".getmypid().PHP_EOL;
  6. // 让父进程停止两秒钟,在这两秒内,子进程的父进程ID还是这个父进程
  7. sleep( 2 );
  8. } else if( 0 == $pid ) {
  9. // 让子进程循环10次,每次睡眠1s,然后每秒钟获取一次子进程的父进程进程ID
  10. for( $i = 1; $i <= 10; $i++ ){
  11. sleep( 1 );
  12. // posix_getppid()函数的作用就是获取当前进程的父进程进程ID
  13. echo posix_getppid().PHP_EOL;
  14. }
  15. } else {
  16. echo "fork error.".PHP_EOL;
  17. }

运行结果如下图:

可以看到,前两秒内,子进程的父进程进程ID为4129,但是从第三秒开始,由于父进程已经提前退出了,子进程变成孤儿进程,所以init进程收养了子进程,所以子进程的父进程进程ID变成了1。

演示并说明僵尸进程的出现,并演示僵尸进程的危害:

  1. <?php
  2. $pid = pcntl_fork();
  3. if( $pid > 0 ){
  4. // 下面这个函数可以更改php进程的名称
  5. cli_set_process_title('php father process');
  6. // 让主进程休息60秒钟
  7. sleep(60);
  8. } else if( 0 == $pid ) {
  9. cli_set_process_title('php child process');
  10. // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
  11. sleep(10);
  12. } else {
  13. exit('fork error.'.PHP_EOL);
  14. }

运行结果如下图:

可以看到,前两秒内,子进程的父进程进程ID为4129,但是从第三秒开始,由于父进程已经提前退出了,子进程变成孤儿进程,所以init进程收养了子进程,所以子进程的父进程进程ID变成了1。

演示并说明僵尸进程的出现,并演示僵尸进程的危害:

  1. <?php
  2. $pid = pcntl_fork();
  3. if( $pid > 0 ){
  4. // 下面这个函数可以更改php进程的名称
  5. cli_set_process_title('php father process');
  6. // 让主进程休息60秒钟
  7. sleep(60);
  8. } else if( 0 == $pid ) {
  9. cli_set_process_title('php child process');
  10. // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
  11. sleep(10);
  12. } else {
  13. exit('fork error.'.PHP_EOL);
  14. }

运行结果如下图:

通过执行ps -aux命令可以看到,当程序在前十秒内运行的时候,php child process的状态列为[S+],然而在十秒钟过后,这个状态变成了[Z+],也就是变成了危害系统的僵尸进程。

那么,问题来了?如何避免僵尸进程呢?PHP通过pcntl_wait()和pcntl_waitpid()两个函数来帮我们解决这个问题。了解Linux系统编程的应该知道,看名字就知道这其实就是PHP把C语言中的wait()和waitpid()包装了一下。

通过代码演示pcntl_wait()来避免僵尸进程,在开始之前先简单普及一下pcntl_wait()的相关内容:这个函数的作用就是 “ 等待或者返回子进程的状态 ”,当父进程执行了该函数后,就会阻塞挂起等待子进程的状态一直等到子进程已经由于某种原因退出或者终止。换句话说就是如果子进程还没结束,那么父进程就会一直等等等,如果子进程已经结束,那么父进程就会立刻得到子进程状态。这个函数返回退出的子进程的进程ID或者失败返回-1。

我们将第二个案例中代码修改一下:
  1. <?php
  2. $pid = pcntl_fork();
  3. if( $pid > 0 ){
  4. // 下面这个函数可以更改php进程的名称
  5. cli_set_process_title('php father process');
  6.  
  7. // 返回$wait_result,就是子进程的进程号,如果子进程已经是僵尸进程则为0
  8. // 子进程状态则保存在了$status参数中,可以通过pcntl_wexitstatus()等一系列函数来查看$status的状态信息是什么
  9. $wait_result = pcntl_wait( $status );
  10. print_r( $wait_result );
  11. print_r( $status );
  12.  
  13. // 让主进程休息60秒钟
  14. sleep(60);
  15. } else if( 0 == $pid ) {
  16. cli_set_process_title('php child process');
  17. // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
  18. sleep(10);
  19. } else {
  20. exit('fork error.'.PHP_EOL);
  21. }

将文件保存为wait.php,然后php wait.php,在另外一个终端中通过ps -aux查看,可以看到在前十秒内,php child process是[S+]状态,然后十秒钟过后进程消失了,也就是被父进程回收了,没有变成僵尸进程。

但是,pcntl_wait()有个很大的问题,就是阻塞。父进程只能挂起等待子进程结束或终止,在此期间父进程什么都不能做,这并不符合多快好省原则,所以pcntl_waitpid()闪亮登场。pcntl_waitpid( Misplaced &status, $option = 0 )的第三个参数如果设置为WNOHANG,那么父进程不会阻塞一直等待到有子进程退出或终止,否则将会和pcntl_wait()的表现类似。

修改第三个案例的代码,但是,我们并不添加WNOHANG,演示说明pcntl_waitpid()功能:

  1. <?php
  2. $pid = pcntl_fork();
  3. if( $pid > 0 ){
  4. // 下面这个函数可以更改php进程的名称
  5. cli_set_process_title('php father process');
  6.  
  7. // 返回值保存在$wait_result中
  8. // $pid参数表示 子进程的进程ID
  9. // 子进程状态则保存在了参数$status中
  10. // 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码
  11. $wait_result = pcntl_waitpid( $pid, $status );
  12. var_dump( $wait_result );
  13. var_dump( $status );
  14.  
  15. // 让主进程休息60秒钟
  16. sleep(60);
  17.  
  18. } else if( 0 == $pid ) {
  19. cli_set_process_title('php child process');
  20. // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
  21. sleep(10);
  22. } else {
  23. exit('fork error.'.PHP_EOL);
  24. }

下面是运行结果,一个执行php程序的终端窗口,另一个是ps -aux终端窗口。实际上可以看到主进程是被阻塞的,一直到第十秒子进程退出了,父进程不再阻塞:

那么我们修改第四段代码,添加第三个参数WNOHANG,代码如下:

  1. <?php
  2. $pid = pcntl_fork();
  3. if( $pid > 0 ){
  4. // 下面这个函数可以更改php进程的名称
  5. cli_set_process_title('php father process');
  6.  
  7. // 返回值保存在$wait_result中
  8. // $pid参数表示 子进程的进程ID
  9. // 子进程状态则保存在了参数$status中
  10. // 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码
  11. $wait_result = pcntl_waitpid( $pid, $status, WNOHANG );
  12. var_dump( $wait_result );
  13. var_dump( $status );
  14. echo "不阻塞,运行到这里".PHP_EOL;
  15.  
  16. // 让主进程休息60秒钟
  17. sleep(60);
  18.  
  19. } else if( 0 == $pid ) {
  20. cli_set_process_title('php child process');
  21. // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
  22. sleep(10);
  23. } else {
  24. exit('fork error.'.PHP_EOL);
  25. }

运行结果,一个执行php程序的终端窗口,另一个是ps -aux终端窗口。实际上可以看到主进程是被阻塞的,一直到第十秒子进程退出了,父进程不再阻塞:

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

问题出现了,竟然php child process进程状态竟然变成了[Z+],这是怎么搞得?回头分析一下代码:

我们看到子进程是睡眠了十秒钟,而父进程在执行pcntl_waitpid()之前没有任何睡眠且本身不再阻塞,所以,主进程自己先执行下去了,而子进程在足足十秒钟后才结束,进程状态自然无法得到回收。如果我们将代码修改一下,就是在主进程的pcntl_waitpid()前睡眠15秒钟,这样就可以回收子进程了。但是即便这样修改,细心想的话还是会有个问题,那就是在子进程结束后,在父进程执行pcntl_waitpid()回收前,有五秒钟的时间差,在这个时间差内,php child process也将会是僵尸进程。那么,pcntl_waitpid()如何正确使用啊?这样用,看起来毕竟不太科学。

那么,是时候引入信号学了!

PHP进程间通信的另外一个手段就是通过 信号 来在进程间传递信息。信号是一种系统调用。通常我们用的kill命令就是发送某个信号给某个进程的。具体有哪些信号可以在liunx/mac中运行kill -l查看。

  1. php的一些信号:
    SIGHUP 终止进程 终端线路挂断
  2.  
  3. SIGINT 终止进程 中断进程
  4.  
  5. SIGQUIT 建立CORE文件终止进程,并且生成core文件
  6.  
  7. SIGILL 建立CORE文件 非法指令
  8.  
  9. SIGTRAP 建立CORE文件 跟踪自陷
  10.  
  11. SIGBUS 建立CORE文件 总线错误
  12.  
  13. SIGSEGV 建立CORE文件 段非法错误
  14.  
  15. SIGFPE 建立CORE文件 浮点异常
  16.  
  17. SIGIOT 建立CORE文件 执行I/O自陷
  18.  
  19. SIGKILL 终止进程 杀死进程
  20.  
  21. SIGPIPE 终止进程 向一个没有读进程的管道写数据
  22.  
  23. SIGALARM 终止进程 计时器到时
  24.  
  25. SIGTERM 终止进程 软件终止信号
  26.  
  27. SIGSTOP 停止进程 非终端来的停止信号
  28.  
  29. SIGTSTP 停止进程 终端来的停止信号
  30.  
  31. SIGCONT 忽略信号 继续执行一个停止的进程
  32.  
  33. SIGURG 忽略信号 I/O紧急信号
  34.  
  35. SIGIO 忽略信号 描述符上可以进行I/O
  36.  
  37. SIGCHLD 忽略信号 当子进程停止或退出时通知父进程
  38.  
  39. SIGTTOU 停止进程 后台进程写终端
  40.  
  41. SIGTTIN 停止进程 后台进程读终端
  42.  
  43. SIGXGPU 终止进程 CPU时限超时
  44.  
  45. SIGXFSZ 终止进程 文件长度过长
  46.  
  47. SIGWINCH 忽略信号 窗口大小发生变化
  48.  
  49. SIGPROF 终止进程 统计分布图用计时器到时
  50.  
  51. SIGUSR1 终止进程 用户定义信号1
  52.  
  53. SIGUSR2 终止进程 用户定义信号2
  54.  
  55. SIGVTALRM 终止进程 虚拟计时器到时

下面来看一个例子。启动3个子进程,运行,父进程等待5秒钟,向子进程发送sigint信号。子进程捕获信号,调用信号处理函数处理。

  1. <?php
  2. /**
  3. * author: NickBai
  4. * createTime: 2016/12/5 0005 下午 3:01
  5. */
  6. $parentPid = posix_getpid();
  7. echo "parent progress pid:{$parentPid}\n";
  8.  
  9. // 定义一个信号处理函数
  10. function sighandler($signo) {
  11. if( $signo == SIGINT ){
  12. $pid = getmypid();
  13. exit("{$pid} progress,oh no ,I'm killed!\n");
  14. }
  15. }
  16.  
  17. //PHP < 5.3 使用
  18. //配合pcntl_signal使用,表示每执行一条低级指令,就检查一次信号,如果检测到注册的信号,就调用其信号处理器。
  19. //declare(ticks=1);
  20.  
  21. pcntl_signal( SIGINT, 'sighandler'); //注册信号处理函数
  22. $childList = [];
  23.  
  24. for( $i = 0; $i < 3; $i++ ){
  25. $pid = pcntl_fork();
  26. if( $pid == 0 ){
  27. while( true ){
  28. //PHP >= 5.3
  29. //调用已安装的信号处理器
  30. //必须在循环里调用,为了检测是否有新的信号等待dispatching。
  31. pcntl_signal_dispatch();
  32.  
  33. echo "i am child " . getmypid() . " and i am running ! \n";
  34. $sec = rand(1,2);
  35. sleep($sec);
  36. }
  37.  
  38. }else if( $pid == -1 ){
  39. exit("fork fail!" . PHP_EOL);
  40. }else{
  41. $childList[$pid] = 1;
  42. }
  43. }
  44.  
  45. sleep(5);
  46. foreach( $childList as $key=>$vo ){
  47. posix_kill( $key, SIGINT ); //触发SIGINIT信号
  48. }
  49. sleep(2);
  50.  
  51. echo "($parentPid)parent is end " . PHP_EOL;

结果如下:

信号详细讲解可参考链接:https://www.cnblogs.com/martini-d/p/9711590.html

PHP多进程的实际处理的更多相关文章

  1. Python中的多进程与多线程(一)

    一.背景 最近在Azkaban的测试工作中,需要在测试环境下模拟线上的调度场景进行稳定性测试.故而重操python旧业,通过python编写脚本来构造类似线上的调度场景.在脚本编写过程中,碰到这样一个 ...

  2. 取代SharedPreferences的多进程解决方案

    Android的SharedPreferences用来存储一些键值对, 但是却不支持跨进程使用. 跨进程来用的话, 当然是放在数据库更可靠啦, 本文主要是给作者的新库PreferencesProvid ...

  3. python 多进程使用总结

    python中的多进程主要使用到 multiprocessing 这个库.这个库在使用 multiprocessing.Manager().Queue时会出问题,建议大家升级到高版本python,如2 ...

  4. Nginx深入详解之多进程网络模型

    一.进程模型        Nginx之所以为广大码农喜爱,除了其高性能外,还有其优雅的系统架构.与Memcached的经典多线程模型相比,Nginx是经典的多进程模型.Nginx启动后以daemon ...

  5. Python的多线程(threading)与多进程(multiprocessing )

    进程:程序的一次执行(程序载入内存,系统分配资源运行).每个进程有自己的内存空间,数据栈等,进程之间可以进行通讯,但是不能共享信息. 线程:所有的线程运行在同一个进程中,共享相同的运行环境.每个独立的 ...

  6. 进击的Python【第十章】:Python的socket高级应用(多进程,协程与异步)

    Python的socket高级应用(多进程,协程与异步)

  7. PHP的pcntl多进程

    PHP使用PCNTL系列的函数也能做到多进程处理一个事务.比如我需要从数据库中获取80w条的数据,再做一系列后续的处理,这个时候,用单进程?你可以等到明年今天了...所以应该使用pcntl函数了. 假 ...

  8. 初探PHP多进程

    h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h ...

  9. gdb进程调试,多进程调试

    1.单进程的调试 常规的通过gdb cmd这种方式开启调试,特别说明的是通过attach的方法附加到一个指定的进程上去进行调试,这种方法适合于调试一个已经运行的进程,具体用法:  gdb -p [pi ...

  10. python高级之多进程

    python高级之多进程 本节内容 多进程概念 Process类 进程间通讯 进程同步 进程池 1.多进程概念 multiprocessing is a package that supports s ...

随机推荐

  1. Leaflet获取可视范围内4个顶点

    //地图级别改变时发生 map.on("zoomend", function (e) { var zoom_val = e.target.getZoom(); map_drag() ...

  2. netty初试

    netty官网:点击进入 学习netty之实现一个丢弃服务器 环境: JDK1.8 netty5.0+ 步骤: 实现一个丢弃服务器 实现一个客户端发送数据 丢弃服务器的创建 //用于接受客户端的的连接 ...

  3. logstash filter geoip 转换IP为详细地址等内容。

    使用logstash geoip筛选器可以将ip地址解析为更丰富的内容. 结果类似于这样: "geoip": { "city_name": "Ürüm ...

  4. Zynq启动流程

    前言 Zynq启动流程和ARM处理器类似,PS部分是启动和配置过程的主设备,芯片引导必须由处理器驱动,系统上电复位后会读取设备模式引脚来决定从什么设备启动芯片.如下表Boot Devices条目所示, ...

  5. 洛谷 P3376 【【模板】网络最大流】

    题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行包含三个正整数ui. ...

  6. react的this.setState没有触发render

    一.浅比较 出现情况: 明明改变了值, 并且回调函数也触发了, 但是就是不触发render import React, { PureComponent } from 'react' import { ...

  7. Laravel Not Found Exceptions 取数据的一个小技巧

    从 model 中取数据的时候, 用下面的方法, 而不是简单的 find(1), 或 first(), 这样如果刚好要查询的数据没有, 就会返回比较友好的 404 页面: $model = App\F ...

  8. aop(权限控制)

    创建sysContext (管理请求) package com.tp.soft.common.util; import javax.servlet.http.HttpServletRequest; i ...

  9. C# 加载DotNetBar组件

    C#作为前端的开发软件,使用的人很多,但是原生的C#界面较为简陋,已经不能满足公司级的开发工作了,今天这篇博客的主要内容是讲一下怎么在C#端使用一个可以提升界面美感的第三方控件,DotNetBar 首 ...

  10. selenium如何屏蔽谷歌浏览器弹出的通知

    使用selenium访问新浪微博的时候  浏览器总会有个通知,需要点击  类似下面这样 下面使用chromeoptions来修改浏览器的设置 from selenium import webdrive ...