ホームページ  >  記事  >  バックエンド開発  >  PHP によるシステム プログラミングの実装: マルチプロセス プログラミングと孤立プロセスとゾンビ プロセスの概要

PHP によるシステム プログラミングの実装: マルチプロセス プログラミングと孤立プロセスとゾンビ プロセスの概要

不言
不言オリジナル
2018-04-13 10:39:411946ブラウズ

この記事の内容は、PHP システムプログラミングにおけるマルチプロセスプログラミングと孤立プロセスとゾンビプロセスの概要を共有することです。必要な友人は参考にしてください。

マルチプロセスプログラミングは。これもシステム プログラミングの一部であり、重要な側面ですが、Web サーバーまたは PHP-FPM がすでにプロセスの問題を管理しているため、通常、PHP プログラマーはマルチプロセスの問題を気にする必要はありませんが、PHP を使用して開発する場合は、 CLIプログラム、マルチプロセスプログラミングは欠かせない基礎技術です。

PHP のプロセス制御方法は主に PCNTL (プロセス制御) 拡張機能を使用するため、マルチプロセス プログラミングを実行する前に、まず PHP に最新の PCNTL 拡張機能がインストールされていることを確認する必要があります。 - 現在インストールされている拡張機能を表示するための m コマンド:



この拡張機能は、プロセス操作のための一連のメソッドを提供します:

PCNTL 函数
pcntl_alarm — 为进程设置一个alarm闹钟信号
pcntl_errno — 别名 pcntl_get_last_error
pcntl_exec — 在当前进程空间执行指定程序
pcntl_fork — 在当前进程当前位置产生分支(子进程)。
pcntl_get_last_error — Retrieve the error number set by the last pcntl function which failed
pcntl_getpriority — 获取任意进程的优先级
pcntl_setpriority — 修改任意进程的优先级
pcntl_signal_dispatch — 调用等待信号的处理器
pcntl_signal_get_handler — Get the current handler for specified signal
pcntl_signal — 安装一个信号处理器
pcntl_sigprocmask — 设置或检索阻塞信号
pcntl_sigtimedwait — 带超时机制的信号等待
pcntl_sigwaitinfo — 等待信号
pcntl_strerror — Retrieve the system error message associated with the given errno
pcntl_wait — 等待或返回fork的子进程状态
pcntl_waitpid — 等待或返回fork的子进程状态
pcntl_wexitstatus — 返回一个中断的子进程的返回代码
pcntl_wifexited — 检查状态代码是否代表一个正常的退出。
pcntl_wifsignaled — 检查子进程状态码是否代表由于某个信号而中断
pcntl_wifstopped — 检查子进程当前是否已经停止
pcntl_wstopsig — 返回导致子进程停止的信号
pcntl_wtermsig — 返回导致子进程中断的信号


pcntl_fork 現在のプロセスの現在位置にブランチ (子プロセス) を作成します。注釈: フォークにより子プロセス、親プロセス、および子プロセスが作成されます。 これらはすべてフォーク位置から開始され、下方向に継続します。違いは、親プロセスの実行中に取得されるフォークの戻り値が子プロセスの番号であり、子プロセスは 0 を取得することです。

フォークされた子プロセスは親プロセスのほぼ完全なコピーですが、親プロセスと子プロセスのデータセグメント、ヒープ、スタックは互いに独立しています。最初に、子プロセスは完全に親プロセスのデータをコピーしますが、その後の変更は相互に影響しません。


int pcntl_fork ( void )


5 つのサブプロセスを作成する コードのデモ:結果:

<?php

for($i = 0; $i < 5; $i++)
{
        $pid = pcntl_fork();  //创建子进程,子进程也是从这里开始执行。

        if ($pid == 0)
        {
                break; //由于子进程也会执行循环的代码,所以让子进程退出循环,否则子进程又会创建自己的子进程。
        }
}

sleep($i);  //第一个创建的子进程将睡眠0秒,第二个将睡眠1s,依次类推...主进程会睡眠5秒

if ($i < 5)
{
        exit("第 " . ($i+1) . " 个子进程退出..." . time() . PHP_EOL);
}
else
{
        exit("父进程退出..." . time() . PHP_EOL);
}


pcntl_fork 関数について理解しておくべき重要な点:

fork は子プロセスを作成します。親プロセスと子プロセスはどちらもフォーク位置から実行を継続します。違いは、親プロセスの実行中にフォークが返されることです。 value は子プロセス番号であり、子プロセスは 0" を取得します


プロセスが終了しないように上記のコードを少し変更し、ps コマンドを使用してシステムのステータスを確認します。


[root@localhost process]# php process.php 
第 1 个子进程退出...1503322773
第 2 个子进程退出...1503322774
第 3 个子进程退出...1503322775
第 4 个子进程退出...1503322776
第 5 个子进程退出...1503322777
父进程退出...1503322778


実行後、 ps -ef | grep php と入力してシス​​テムプロセスを表示します

<?php

for($i = 0; $i < 5; $i++)
{
        $pid = pcntl_fork();

        if ($pid == 0)
        {
                break; //由于子进程也会执行循环的代码,所以让子进程退出循环
        }
}

sleep($i);  //第一个创建的子进程将睡眠0秒,第二个将睡眠1s,依次类推...主进程会睡眠5秒

/*
if ($i < 5)
{
        exit("第 " . ($i+1) . " 个子进程退出..." . time() . PHP_EOL);
}
else
{
        exit("父进程退出..." . time() . PHP_EOL);
}
*/

while(1)
{
        sleep(1);       //执行死循环不退出
}


2番目の列に6つのphp process.phpプロセスが表示されます。はプロセス番号、3列目はプロセスの親プロセス番号です。次の5つのプロセスの親プロセス番号はすべて最初のプロセスのプロセス番号であることがわかります。


上記のコードは、子プロセスと親プロセスの両方で同じコードを実行しますが、子プロセスと親プロセスが異なることを行うようにする方法はありますか?プロセスは子プロセスを実行します 親プロセスのコードは親プロセスのコードを実行します:


[root@localhost ~]# ps -ef | grep php
root      3670  3609  0 21:54 pts/0    00:00:00 php process.php
root      3671  3670  0 21:54 pts/0    00:00:00 php process.php
root      3672  3670  0 21:54 pts/0    00:00:00 php process.php
root      3673  3670  0 21:54 pts/0    00:00:00 php process.php
root      3674  3670  0 21:54 pts/0    00:00:00 php process.php
root      3675  3670  0 21:54 pts/0    00:00:00 php process.php
root      3677  3646  0 21:54 pts/1    00:00:00 grep php

<?php
$ppid = posix_getpid(); //记录父进程的进程号

for($i = 0; $i < 5; $i++)
{
        $pid = pcntl_fork();

        if ($pid == 0)
        {       
                break; //由于子进程也会执行循环的代码,所以让子进程退出循环
        }
}

if ($ppid == posix_getpid())
{
        //父进程
        while(1)
        {
                sleep(1);
        }
}
else
{
        //子进程
        for($i = 0; $i < 100; $i ++)
        {
                echo "子进程" . posix_getpid() . " 循环 $i ...\n";
                sleep(1);
        }
}

実際、上記のプログラムの親プロセスと子プロセスは同じコードを実行します。 , ただし、入力される if 分岐が異なり、pcntl_exec は子プロセスを親プロセスの影響から完全に逃れて新しいプログラムを実行できます。

pcntl_exec — 在当前进程空间执行指定程序

void pcntl_exec ( string $path [, array $args [, array $envs ]] )

path
path必须时可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本(比如文件第一行是#!/usr/local/bin/perl的perl脚本)。 更多的信息请查看您系统的execve(2)手册。

args
args是一个要传递给程序的参数的字符串数组。

envs
envs是一个要传递给程序作为环境变量的字符串数组。这个数组是 key => value格式的,key代表要传递的环境变量的名称,value代表该环境变量值。

注意该方法的返回值比较特殊:当发生错误时返回 FALSE ,没有错误时没有返回,因为pcntl_exec调用成功,子进程就去运行新的程序 从父进程继承的代码段、数据段、堆、栈等信息全部被替换成新的,此时的pcntl_exec函数调用栈已经不存在了,所以也就没有返回了。代码示例:

<?php
for($i = 0; $i < 3; $i++)
{
        $pid = pcntl_fork();

        if($pid == 0)
        {
                echo "子进程pid = " . posix_getpid() . PHP_EOL;
                $ret = pcntl_exec(&#39;/bin/ls&#39;);  //执行 ls 命令, 此处调用成功子进程将不会再回来执行下面的任何代码
                var_dump($ret); // 此处的代码不会再执行
        }
}

sleep(5);  //睡眠5秒以确保子进程执行完毕,原因后面会说

exit( "主进程退出...\n");

运行结果:

[root@localhost process]# php pcntl_exec.php 
子进程pid = 6728
子进程pid = 6729
子进程pid = 6727
pcntl_exec.php	process.php
pcntl_exec.php	process.php
pcntl_exec.php	process.php
主进程退出...
[root@localhost process]# ls
pcntl_exec.php  process.php

以上就是对PHP多进程开发的简单介绍,对于子进程不同的存续状态,引出孤儿进程和僵尸进程的概念,在linux系统中,init进程(1号进程)是所有进程的祖先,其他进程要么是该进程的子进程,要么是子进程的子进程,子进程的子进程的子进程...,linux系统中可以用 pstree 命令查看进程树结构:


在多进程程序中,如果父进程先于子进程退出,那么子进程将会被init进程收养,成为init进程的子进程,这种进程被称为孤儿进程,我们可以把上面的代码稍作修改来演示这种情况:


<?php
$ppid = posix_getpid(); //记录父进程的进程号

for($i = 0; $i < 5; $i++)
{
        $pid = pcntl_fork();

        if ($pid == 0)
        {       
                break; //由于子进程也会执行循环的代码,所以让子进程退出循环
        }
}

if ($ppid == posix_getpid())
{
        //父进程直接退出,它的子进程都会成为孤儿进程
        exit(0);
}
else
{
        //子进程
        for($i = 0; $i < 100; $i ++)
        {
                echo "子进程" . posix_getpid() . " 循环 $i ...\n";
                sleep(1);
        }
}

运行该程序,然后查看进程状态:


[root@localhost ~]# ps -ef | grep php
root      2903     1  0 12:09 pts/0    00:00:00 php pcntl.fork.php
root      2904     1  0 12:09 pts/0    00:00:00 php pcntl.fork.php
root      2905     1  0 12:09 pts/0    00:00:00 php pcntl.fork.php
root      2906     1  0 12:09 pts/0    00:00:00 php pcntl.fork.php
root      2907     1  0 12:09 pts/0    00:00:00 php pcntl.fork.php
root      2935  2912  0 12:10 pts/1    00:00:00 grep php


可以看到五个子进程的父进程号都是1了,并且这时控制台不再被程序占用,子进程转到了后台运行,这种孤儿进程被init进程收养的机制是实现后面将要介绍的守护进程的必要条件之一。

子进程还有一种状态叫僵尸进程,子进程结束时并不是完全退出,内核进程表中仍旧保有该进程的记录,这样做的目的是能够让父进程可以得知子进程的退出状态,以及子进程是自杀(调用exit或代码执行完毕)还是他杀(被信号终止),父进程可以调用pcntl_wait 或 pcntl_waitpid 方法来回收子进程(收尸),释放子进程占用的所有资源,并获得子进程的退出状态,如果父进程不做回收,则僵尸进程一直存在,如果这时父进程也退出了,则这些僵尸进程会被init进程接管并自动回收。

对于linux系统来说,一个长时间运行的多进程程序一定要回收子进程,因为系统的进程资源是有限的,僵尸进程会让系统的可用资源减少。

代码演示僵尸进程的产生:

<?php
$ppid = posix_getpid(); //记录父进程的进程号

for($i = 0; $i < 5; $i++)
{
        $pid = pcntl_fork();

        if ($pid == 0)
        {
                break; //由于子进程也会执行循环的代码,所以让子进程退出循环
        }
}

if ($ppid == posix_getpid())
{
        //父进程不退出,也不回收子进程
        while(1)
        {
                sleep(1);
        }
}
else
{
        //子进程退出,会成为僵尸进程
                exit("子进程退出 $ppid ...\n");
}

运行之后查看进程状态:

[root@localhost ~]# ps -ef | grep php
root      2971  2864  0 14:13 pts/0    00:00:00 php pcntl.fork.php
root      2972  2971  0 14:13 pts/0    00:00:00 [php] <defunct>
root      2973  2971  0 14:13 pts/0    00:00:00 [php] <defunct>
root      2974  2971  0 14:13 pts/0    00:00:00 [php] <defunct>
root      2975  2971  0 14:13 pts/0    00:00:00 [php] <defunct>
root      2976  2971  0 14:13 pts/0    00:00:00 [php] <defunct>
root      2978  2912  0 14:13 pts/1    00:00:00 grep php


僵尸进程会用 21eba13340b1d3a525d7480ca5bbd370(死者,死人) 来标识,除非我们结束父进程,否则这些僵尸进程会一直存在,也无法用kill命令来杀死。

PHP的pcntl扩展提供了两个回收子进程的方法供我们调用:



int pcntl_wait ( int &$status [, int $options = 0 ] )
int pcntl_waitpid ( int $pid , int &$status [, int $options = 0 ] )


pcntl_wait函数挂起当前进程的执行直到一个子进程退出或接收到一个信号要求中断当前进程或调用一个信号处理函数。 如果一个子进程在调用此函数时已经退出(俗称僵尸进程),此函数立刻返回。子进程使用的所有系统资源将被释放。
关于wait在您系统上工作的详细规范请查看您系统的wait(2)手册。这个函数等同于以-1作为参数pid 的值并且没有options参数来调用pcntl_waitpid() 函数。

代码示例:

<?php
$ppid = posix_getpid(); //记录父进程的进程号

for($i = 0; $i < 5; $i++)
{
        $pid = pcntl_fork();

        if ($pid == 0)
        {
                break; //由于子进程也会执行循环的代码,所以让子进程退出循环
        }
}

if ($ppid == posix_getpid())
{
        //父进程循环回收收子进程
        while(($id = pcntl_wait($status)) > 0) //如果没有子进程退出, pcntl_wait 会一直阻塞
        {
                echo "回收子进程:$id, 子进程退出状态值: $status...\n";
        }

        exit("父进程退出 $id....\n"); //当子进程全部结束 pcntl_wait 返回-1
}
else
{
        //子进程退出,会成为僵尸进程
        sleep($i);
        exit($i); 
}


运行结果:

[root@localhost php]# php pcntl.fork.php 
回收子进程:3043, 子进程退出状态值: 0...
回收子进程:3044, 子进程退出状态值: 256...
回收子进程:3045, 子进程退出状态值: 512...
回收子进程:3046, 子进程退出状态值: 768...
回收子进程:3047, 子进程退出状态值: 1024...
父进程退出 -1....


这里只是对PHP多进程编程做了基本的介绍,后面会结合 信号、进程间通信以及守护进程 做更进一步的介绍,欢迎大家关注后续文章。

PHP是世界上最好的语言 That's all :)

相关推荐:

PHP实现系统编程之网络Socket及IO多路复用

以上がPHP によるシステム プログラミングの実装: マルチプロセス プログラミングと孤立プロセスとゾンビ プロセスの概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。