zoukankan      html  css  js  c++  java
  • PHP多进程初步

    一、前言

    我们都知道PHP是单线程执行,处理多并发主要是依赖服务器或PHP-FPM的多进程及它们进程的复用,但PHP实现多进程也意义重大,尤其是在后台Cli模式下处理大量数据或运行后台DEMON守护进程时。不能应用在Web服务器环境。

    /** 检测是否CLI模式,确保这个函数只能运行在SHELL中 */
    if (substr(php_sapi_name(), 0, 3) !== 'cli') {
      die("cli mode only");
    }

    日常任务中,有时需要通过php脚本执行一些日志分析,队列处理等任务,当数据量比较大时,可以使用多进程来处理。

    PHP的多线程也曾被人提及,但进程内多线程资源共享和分配的问题难以解决。PHP也有多线程想关的扩展 pthreads ,但据说不太稳定,且要求环境为线程安全,所用不多。

    要实现PHP的多进程,需要安装 pcntl 和 posix 扩展。

    二、创建子进程

    使用 pcntl_fork() 函数可以在当前位置产生分支。fork 是创建了一个子进程,父进程和子进程都从 fork 的位置开始向下继续执行,不同的是父进程执行过程中,得到的 fork 返回值为子进程号,而子进程得到的是0,执行失败则返回-1。

    因为系统初始init进程的pid为1,后来的所有进程pid都会大于该进程,所以可以通过 pcntl_fork() 的返回值大于1来判断当前进程是父进程,返回值等于0来判断是子进程。

    $ppid = posix_getpid(); // 获取当前进程的id
    $pid = pcntl_fork();  // 创建子进程
    if ($pid == -1) {
        throw new Exception('fork子进程失败!');
    } elseif ($pid > 0) {
        // 父进程执行逻辑
        cli_set_process_title("我是父进程,我的进程id是{$ppid}.");
        sleep(30); 
        pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
    } else {
        // 子进程执行逻辑
        $cpid = posix_getpid();
        cli_set_process_title("我是{$ppid}的子进程,我的进程id是{$cpid}.");
        sleep(30);
    }

    执行结果:

    注意:如果是在循环中创建子进程,那么子进程中最后要 exit 退出,防止子进程进入循环。

    三、管理子进程

    管理子进程,使用的是信号。简单来说,就是父进程里使用两个函数 pcntl_signal() 和 pcntl_signal_dispatch(),负责给子进程安装信号处理器和分发工作。

    在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。

    我们通过在父进程接收子进程传来的信号,判断子进程状态,来对子进程进行管理。我们需要在父进程里使用 pcntl_signal() 函数和 pcntl_signal_dispatch() 函数来给各个子进程安装信号处理器:

    // 安装一个信号处理器,$signo是待处理的信号常量,callback是其处理函数
    pcntl_signal (int $signo , callback $handler) 
    // 调用每个等待信号通过pcntl_signal()安装的处理器 pcntl_signal_dispatch ()

    PHP内常见的信号常量有:

    • SIGCHLD:子进程退出成为僵尸进程会向父进程发送此信号
    • SIGHUP:进程挂起
    • SIGTEM:进程终止

    四、处理子进程

    处理子进程,需要两个函数:

    // 向进程id为$pid的进程发送$sig信号
    bool posix_kill ( int $pid, int $sig )
    //挂起当前进程的执行直到进程号为$pid的进程退出(如果$pid为-1,则等待任意一个子进程) int pcntl_waitpid ( int $pid, int &$status [, int $options = 0 ] )

    posix_kill() 函数通过向子进程发送一个信号来操作子进程,在需要要时可以选择给子进程发送进程终止信号来终止子进程;

    pcntl_waitpid() 函数等待或返回 fork 的子进程状态,如果指定的子进程在此函数调用时已经退出(俗称僵尸进程),此函数将立刻返回,并释放子进程的所有系统资源,此进程可以避免子进程变成僵尸进程,造成系统资源浪费。这样就可以实现跟子进程共同完成的任务的目的了。

    五、实例

    如果一个任务被分解成多个进程执行,就会减少整体的耗时。比如有一个比较大的数据文件要处理,这个文件由很多行组成。如果单进程执行要处理的任务,量很大时要耗时比较久。这时可以考虑多进程。多进程处理分解任务,每个进程处理文件的一部分,这样需要均分割一下这个大文件成多个小文件(进程数和小文件的个数等同就可以)。

    比如文件 file.log 有10万行数据,现在想分4个进程处理。需要分割2.5万行一个文件。命令 split 可以做到:

    <?php
    
    shell_exec('split -l 25000 -d file.log prefix_name');
    
    // 3个子进程处理任务
    for ($i = 0; $i < 3; $i++){
        $pid = pcntl_fork();
    
        if ($pid == -1) {
            die("could not fork");
        } elseif ($pid) {
            echo "I'm the Parent $i
    ";
        } else {// 子进程处理
            $content = file_get_contents("prefix_name0".$i);
            // 业务处理 begin
    
            // 业务处理 end
            exit; // 一定要注意退出子进程,否则pcntl_fork()会被子进程再fork,带来处理上的影响。
        }
    }
    
    // 等待子进程执行结束
    while (pcntl_waitpid(0, $status) != -1) {
        $status = pcntl_wexitstatus($status);
        echo "Child $status completed
    ";
    }

    参考:

    《php多进程总结》:https://www.cnblogs.com/leezhxing/p/5223289.html

    《初探PHP多进程》:https://www.cnblogs.com/zhenbianshu/p/5676822.html

    《PHP利用多进程处理任务》:https://www.cnblogs.com/firstForEver/p/7301630.html

  • 相关阅读:
    LeetCode 55
    LeetCode 337
    LeetCode 287
    LeetCode 274
    LeetCode 278
    LeetCode 264
    LeetCode 189
    LeetCode 206
    LeetCode 142
    LeetCode 88
  • 原文地址:https://www.cnblogs.com/tangxuliang/p/9208133.html
Copyright © 2011-2022 走看看