PHP进程间通信-信号

PHP进程间通信-信号

大家好,又见面了,我是全栈君。

点击上方“码农编程进阶笔记”,选择“置顶或者星标

优质文章第一时间送达!

(一)PHP进程间通信-信号
信号是一种系统调用。通常我们用的kill命令就是发送某个信号给某个进程的。具体有哪些信号可以在liunx/mac中运行kill -l查看。下面这个例子中,父进程等待5秒钟,向子进程发送sigint信号。子进程捕获信号,调信号处理函数处理。

代码演示

<?php
$childList = [];
$parentId = posix_getpid();

//信号处理函数
function signHandler($sign){
    $pid = posix_getpid();
    exit("process:{$pid},is killed,signal is {$sign}\n");
}

$pid = pcntl_fork();
if ($pid == -1){
    // 创建子进程失败
    exit("fork fail,exit!\n");
}elseif ($pid == 0){
    //子进程执行程序
    //注册信号处理函数
    declare(ticks = 10);
    pcntl_signal(SIGINT,"signHandler");//注册SIGINT信号处理函数
    $pid = posix_getpid();
    while (true){
        echo "child process {$pid},is running.......\n";
        sleep(1);
    }
}else{
    $childList[$pid] = 1;
    sleep(5);
    posix_kill($pid,SIGINT);//向指定进程发送一个信号
}

// 等待子进程结束
while(!empty($childList)){
    $pid = pcntl_wait($status);
    if ($pid > 0){
        unset($childList[$pid]);
    }
}

echo "The child process is killed by parent process {$parentId}\n";

运行结果
PHP进程间通信-信号
当父进程没有发送信号的时候,子进程会一直循环输出‘child process is running…’,父进程发送信号后,子进程在检查到有信号进来的时候调用对应的回调函数处理退出了子进程。

declare(ticks = 10)
这里的ticks=10,可以理解为程序执行10条低级语句后,检查看有没有未执行的信号,有的话就去处理。
关于declare(ticks = n)的详细讲解可以参考这篇文章

(二)初探

信号是一种软件中断,也是一种非常典型的异步事件处理方式。在NIX系统诞生的混沌之初,信号的定义是比较混乱的,而且最关键是不可靠,这是一个很严重的问题。所以在后来的POSIX标准中,对信号做了标准化同时也各个发行版的NIX也都提供大量可靠的信号。每种信号都有自己的名字,大概如SIGTERM、SIGHUP、SIGCHLD等等,在*NIX中,这些信号本质上都是整形数字(游有心情的可以参观一下signal.h系列头文件)。

信号的产生是有多种方式的,下面是常见的几种:

  • 键盘上按某些组合键,比如Ctrl+C或者Ctrl+D等,会产生SIGINT信号。

  • 使用posix kill调用,可以向某个进程发送指定的信号。

  • 远程ssh终端情况下,如果你在服务器上执行了一个阻塞的脚本,正在阻塞过程中你关闭了终端,可能就会产生SIGHUP信号。

  • 硬件也会产生信号,比如OOM了或者遇到除0这种情况,硬件也会向进程发送特定信号。

 而进程在收到信号后,可以有如下三种响应:

  • 直接忽略,不做任何反映。就是俗称的完全不鸟。但是有两种信号,永远不会被忽略,一个是SIGSTOP,另一个是SIGKILL,因为这两个进程提供了向内核最后的可靠的结束进程的办法。

  • 捕捉信号并作出相应的一些反应,具体响应什么可以由用户自己通过程序自定义。

  • 系统默认响应。大多数进程在遇到信号后,如果用户也没有自定义响应,那么就会采取系统默认响应,大多数的系统默认响应就是终止进程。

 

用人话来表达,就是说假如你是一个进程,你正在干活,突然施工队的喇叭里冲你嚷了一句:“吃饭了!”,于是你就放下手里的活儿去吃饭。你正在干活,突然施工队的喇叭里冲你嚷了一句:“发工资了!”,于是你就放下手里的活儿去领工资。你正在干活,突然施工队的喇叭里冲你嚷了一句:“有人找你!”,于是你就放下手里的活儿去看看是谁找你什么事情。当然了,你很任性,那是完全可以不鸟喇叭里喊什么内容,也就是忽略信号。也可以更任性,当喇叭里冲你嚷“吃饭”的时候,你去就不去吃饭,你去睡觉,这些都可以由你来。而你在干活过程中,从来不会因为要等某个信号就不干活了一直等信号,而是信号随时随地都可能会来,而你只需要在这个时候作出相应的回应即可,所以说,信号是一种软件中断,也是一种异步的处理事件的方式。

回到上文所说的问题,就是子进程在结束前,父进程就已经先调用了pcntl_waitpid(),导致子进程在结束后依然变成了僵尸进程。实际上在父进程不断while循环调用pcntl_waitpid()是个解决办法,大概代码如下:

$pid = pcntl_fork();
if( 0 > $pid ){
  exit('fork error.'.PHP_EOL);
} else if( 0 < $pid ) {
  // 在父进程中
  cli_set_process_title('php father process');
  // 父进程不断while循环,去反复执行pcntl_waitpid(),从而试图解决已经退出的子进程
  while( true ){
    sleep( 1 );
    pcntl_waitpid( $pid, &$status, WNOHANG );
  }
} else if( 0 == $pid ) {
  // 在子进程中
  // 子进程休眠3秒钟后直接退出
  cli_set_process_title('php child process');
  sleep( 20 );
  exit;
}

下图是运行结果:
PHP进程间通信-信号

解析一下这个结果,我先后三次执行了ps -aux | grep php去查看这两个php进程。
  • 第一次:子进程正在休眠中,父进程依旧在循环中。

  • 第二次:子进程已经退出了,父进程依旧在循环中,但是代码还没有执行到pcntl_waitpid(),所以在子进程退出后到父进程执行回收前这段空隙内子进程变成了僵尸进程。

  • 第三次:此时父进程已经执行了pcntl_waitpid(),将已经退出的子进程回收,释放了pid等资源。

 

但是这样的代码有一个缺陷,实际上就是子进程已经退出的情况下,主进程还在不断while pcntl_waitpid()去回收子进程,这是一件很奇怪的事情,并不符合社会主义主流价值观,不低碳不节能,代码也不优雅,不好看。所以,应该考虑用更好的方式来实现。那么,我们篇头提了许久的信号终于概要出场了。

现在让我们考虑一下,为何信号可以解决“不低碳不节能,代码也不优雅,不好看”的问题。子进程在退出的时候,会向父进程发送一个信号,叫做SIGCHLD,那么父进程一旦收到了这个信号,就可以作出相应的回收动作,也就是执行pcntl_waitpid(),从而解决掉僵尸进程,而且还显得我们代码优雅好看节能环保。

梳理一下流程,子进程向父进程发送SIGCHLD信号是对人们来说是透明的,也就是说我们无须关心。但是,我们需要给父进程安装一个响应SIGCHLD信号的处理器,除此之外,还需要让这些信号处理器运行起来,安装上了不运行是一件尴尬的事情。那么,在php里给进程安装信号处理器使用的函数是pcntl_signal(),让信号处理器跑起来的函数是pcntl_signal_dispatch()。

  • pcntl_signal(),安装一个信号处理器,具体说明是pcntl_signal     ( int $signo , callback $handler [, bool $restart_syscalls = true ] ),参数signo就是信号,callback则是响应该信号的代码段,返回bool值。

  • pcntl_signal_dispatch(),调用每个等待信号通过pcntl_signal()     安装的处理器,参数为void,返回bool值。

 

下面结合新引入的两个函数来解决一下楼上的丑陋代码:

$pid = pcntl_fork();
if( 0 > $pid ){
  exit('fork error.'.PHP_EOL);
} else if( 0 < $pid ) {
  // 在父进程中
  // 给父进程安装一个SIGCHLD信号处理器
  pcntl_signal( SIGCHLD, function() use( $pid ) {
    echo "收到子进程退出".PHP_EOL;
    pcntl_waitpid( $pid, $status, WNOHANG );
  } );
  cli_set_process_title('php father process');
  // 父进程不断while循环,去反复执行pcntl_waitpid(),从而试图解决已经退出的子进程
  while( true ){
    sleep( 1 );
    // 注释掉原来老掉牙的代码,转而使用pcntl_signal_dispatch()
    //pcntl_waitpid( $pid, &$status, WNOHANG );
    pcntl_signal_dispatch();
  }
} else if( 0 == $pid ) {
  // 在子进程中
  // 子进程休眠3秒钟后直接退出
  cli_set_process_title('php child process');
  sleep( 20 );
  exit;
}

运行结果如下:
PHP进程间通信-信号
PHP进程间通信-信号

PHP进程间通信-信号

PHP进程间通信-信号

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/112018.html原文链接:https://javaforall.net

(0)
上一篇 2022年2月11日 下午6:00
下一篇 2022年2月11日 下午6:00


相关推荐

  • Redis集群搭建(非常详细)

    Redis集群搭建(非常详细)https blog csdn net article details redis 集群搭建在开始 redis 集群搭建之前 我们先简单回顾一下 redis 单机版的搭建过程 下载 redis 压缩包 然后解压压缩文件 进入到解压缩后的 redis 文件目录 此时可以看到 Makefile 文件 编译 redis 源文件 把编译好的 redis 源文件安装到 usr local redis 目录下 如果 local 目录下没有 redis 目录 会自动新建 r

    2025年10月28日
    6
  • TPS、QPS和系统吞吐量的区别和理解

    TPS、QPS和系统吞吐量的区别和理解一 QPS TPSQPS QueriesPerSe 意思是 每秒查询率 是一台服务器每秒能够相应的查询次数 是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准 TPS 是 Transactions 的缩写 也就是事务数 秒 它是软件测试结果的测量单位 一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程 客户机在发送请求时开始计时 收到服务器响

    2026年3月26日
    2
  • kali密码爆破工具_目录对比工具

    kali密码爆破工具_目录对比工具一、下载与安装百度云下载链接:https://pan.baidu.com/s/1Df4wJy2jZ3kRx_aROWy1PQ(提取码:vfcn) 下载之后解压然后使用工具将解压后的目录移动到Linux中(此处我们选择Kali)进入dirsearch-maser目录之后,其中的“dirsearch.py”就是我们可以使用的dirsearch工具,但是我们没有权限执行该文件,所以需…

    2022年10月5日
    4
  • Java获取年月日

    Java获取年月日publicclassT publicstatic String args 1 普通的时间转换 Stringstring newSimpleDat yyyy MM dd format newDate toString System out println str

    2026年3月20日
    2
  • Maven相关配置

    Maven相关配置

    2021年7月11日
    101
  • sql中的declare_如何声明变量

    sql中的declare_如何声明变量在sql语句中添加变量。declare @local_variabledata_type声明时需要指定变量的类型,可以使用set和select对变量进行赋值,在sql语句中就可以使用@local_variable来调用变量 声明中可以提供值,否则声明之后所有变量将初始化为NULL。 例如:declare@idint

    2022年8月20日
    12

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号