この記事では、PHP に関する関連知識を提供します。主に PHP マルチプロセス開発に関連する問題を紹介します。ここでは、マルチプロセス開発の問題の概要と回答を示します。見てみましょう。皆さんが知っていることを願っています。助けるために。
PHP マルチプロセス開発
まずは簡単なコマンドをいくつか紹介
echo $$ //输出当前bash进程 strace -s 65500 -p 进程号 //打印进程系统调用 kill -s 10 pid //发送信号 kill -s SIGUSR2 pid //发送信号 pstree -ap //查看进程树 ps -ajx //查看进程信息 ps 命令字段解析: PPID:父进程ID PID:进程ID PGID:进程组ID SID:会话ID TTY:所在终端 STAT:进程状态 R运行 Z僵尸 S睡眠 T停止 D睡眠无法被唤醒 UID:unix用户ID COMMAND:启动命令
プログラムとは何ですか?
一般に、実行可能ファイルを指します。Linux システムでは、ELF 形式で保存されます。接尾辞はまったくありません。file コマンドは、elf ファイルの特定の種類を表示できます。
ELF 完全に実行可能なリンク可能な形式 実行可能なリンク可能な形式
ELF は 4 つの主要なカテゴリに分類されます
EXEC 実行可能ファイル
REL リロケータブル ファイル (静的ライブラリ ファイルとも呼ばれます) は、リンカがリンクした後、event.soソケット.socurl.so
共有オブジェクトなどの動的ライブラリ ファイルになります。ファイル共有オブジェクト ファイル
コア ダンプには、プロセスによって生成された例外情報が保存されます
php ビデオ チュートリアル]
ターミナルとは何ですか?
/dev /console 現在フォーカスされている端末
プロセスとは何ですか?
プロセスが終了します
ステートメントの最後の行まで実行します
#実行中に exit() 関数に遭遇した場合
#プログラムが異常な場合
プロセスは割り込み信号を受信します
#子プロセスの実行後、親プロセスはリサイクルのために pcntl_wait () を呼び出さず、プロセスのステータスは Z
デーモン プロセス親プロセスは init プロセスであり、強制的に終了しない限り、通常はシステムの起動時に実行を開始し、システムがシャットダウンされるまで実行され続けます。デーモンは特別なポート (1 ~ 1024) を使用したり、特定のリソースにアクセスしたりするため、スーパーユーザー (root) 権限で実行されることがよくあります。
プロセス グループとは何ですか?複数のプロセスがプロセス グループを形成します。各プロセス グループにはグループ リーダーが 1 つだけあります。グループ リーダーの PID はプロセス グループの ID です。プロセス グループは、プロセス グループ内のすべてのプロセスが終了した場合にのみ消滅します。 ps -ajx コマンドを使用して pgid を表示できます
セッションとは何ですか?
複数のプロセス グループがセッションを形成し、各セッションにはセッション先頭プロセスがあります。セッションの機能1) 新しいセッションを作成するには、setsid () 関数を使用します
2) セッションの最初のプロセスは setid を呼び出すことができないため、エラーが報告されます3 ) 非セッションの最初のプロセス プロセスは、setsid を呼び出して新しいセッションを作成できます。この動作により、プロセスは新しいプロセス グループを作成し、それ自体がプロセス グループ リーダーになります。プロセスは新しいセッション グループを作成し、それ自体がプロセス グループ リーダーになります。セッション グループ リーダー。チーム リーダー、プロセスは現在のコマンド ライン コントロール ターミナルから分離されます。
実際のたとえとしては、上司を除く従業員は私に電話をかけることができ、私はそれを行うことができます()。この関数は
シグナルとは何ですか?シグナルはプロセス間通信の方法の 1 つです。通常使用される kill -9 pid は、9 番目の方法でプロセスを強制終了することを意味するのではなく、シグナル値が のシグナルを送信することを意味します。 9. プロセスに対して、シグナル 9 がたまたま SIGKILL である場合、その機能はプロセスを停止し、オペレーティング システムでサポートされているシグナル コマンドを確認します: kill -l
通常は 1 ~ 31 を使用しますが、32、33 があるかどうかに注意してください。これら 2 つの信号のソースは次のとおりです。
信号は次のとおりです。键盘上按了 Ctrl+C 会产生 SIGINT 信号,关闭终端会产生 SIGHUP 信号
硬件也能产生信号
使用 kill 命令
软件产生,比如在管道里当一侧准备写管道时可能会产生 SIGPIPE 信号
当一个进程收到一个信号时,三个可选操作
操作系统默认的方式,比如 SIGKILL 就是杀死进程
忽略掉这个信号,pcntl_signal (SIGKILL, SIG_IGN, false); 进程收到 SIGKILL 命令时将不为所动
有自己的想法,pcntl_signal (SIGKILL, function ($signal){// 自己的想法}, false); 这样将会触发自定义回调
pcntl_signal () 信号处理器是会被子进程继承的,所以 fork () 之前最后先行处理信号处理器
posix 命令
//需要安装posix扩展 posix_getpid(); //获取进程ID posix_getppid();//获取父进程ID posix_getpgid(posix_getppid());//获取进程组ID posix_getpgrp());//同上 posix_getsid(posix_getpid()));//获取会话ID posix_getuid();//获取当前登录用户UID posix_getgid();//获取当前登录用户组UID posix_geteuid();//获取当前有效用户UID posix_getguid();//获取当前有效用户组UID posix_kill();//发送信号
pcntl 命令
//创建一个计时器,在指定的秒数后向进程发送一个SIGALRM信号。每次对 pcntl_alarm()的调用都会取消之前设置的alarm信号。如果seconds设置为0,将不会创建alarm信号。 pcntl_alarm(int $seconds); //在当前进程当前位置产生子进程,子进程会复制父进程的代码段和数据段(Copy on write 写时复制,当子进程要修改内存空间时,操作系统会分配新的内存给子进程),ELF文件的结构,如果父进程先退出,子进程变成孤儿进程,被pid=1进程接管 pcntl_fork(); //安装一个信号处理器 pcntl_signal(int $signo, callback $handler); //调用等待信号的处理器,触发全部未执行的信号回调 pcntl_signal_dispatch() //设置或检索阻塞信号 pcntl_sigprocmask(int $how, array $set[, array &$oldset]) //等待或返回fork的子进程状态,wait函数挂起当前进程的执行直到一个子进程退出或接收到一个信号要求中断当前进程或调用一个信号处理函数。用此函数时已经退出(俗称僵尸进程),此函数立刻返回。子进程使用的所有系统资源将被释放。 pcntl_wait($status) //加个WNOHANG参数,不挂起父进程,如果没有子进程退出返回0,如果有子进程退出返回子进程pid,如果返回-1表示父进程已经没有子进程 pcntl_wait($status, WNOHANG) //基本同pcntl_wait,waitpid可以指定子进程id pcntl_waitpid ($pid ,$status) pcntl_waitpid ($pid ,$status, WNOHANG) //检查状态代码是否代表一个正常的退出。参数 status 是提供给成功调用 pcntl_waitpid() 时的状态参数。 pcntl_wifexited($status) //返回一个中断的子进程的返回代码 当php exit(10)时,这个函数返回10,这个函数仅在函数pcntl_wifexited()返回 TRUE.时有效 pcntl_wexitstatus($status) //检查子进程状态码是否代表由于某个信号而中断。参数 status 是提供给成功调用 pcntl_waitpid() 时的状态参数。 pcntl_wifsignaled($status) //返回导致子进程中断的信号 pcntl_wtermsig($status) //检查子进程当前是否已经停止,此函数只有作用于pcntl_wait使用了WUNTRACED作为 option的时候 pcntl_wifstopped($status) //返回导致子进程停止的信号 pcntl_wstopsig($status) //检索由最后一个失败的pcntl函数设置的错误数 pcntl_errno() pcntl_get_last_error() //检索与给定errno关联的系统错误消息 pcntl_strerror(pcntl_errno())
pcntl_fork () 执行之前先与 Redis 建立一个连接,然后再开 3 个子进程之后多少个 Redis 连接?
<?php $o_redis = new Redis(); $o_redis->connect( '127.0.0.1', 6379 ); // 使用for循环搞出3个子进程来 for ( $i = 1; $i <= 3; $i++ ) { $i_pid = pcntl_fork(); if ( 0 == $i_pid ) { // 使用while保证三个子进程不会退出... while( true ) { sleep( 1 ); } } } // 使用while保证主进程不会退出... while( true ) { sleep( 1 ); } netstat -ant |grep 6379
说明父进程和三个子进程一共四个进程,实际上共享了一个 Redis 长连接
上面这种写法会有什么问题?
因为 Redis 是一个单进程单线程的服务器,所以接收到的命令都是顺序执行顺序返回的,所以当客户端多个进程共享一个 redis 连接时,当有四个进程向 Redis 服务端发起请求,返回四个结果,谁先抢到就是谁的,正确的做法是每个子进程创建一个 Redis 连接,或者用连接池
孤儿进程怎么产生?
$i_pid = pcntl_fork(); if (0 == $i_pid) { // 子进程10秒钟后退出. for ($i = 1; $i <= 10; $i++) { sleep(1); echo "我的父进程是:" . posix_getppid() . PHP_EOL; } } else if ($i_pid > 0) { // 父进程休眠2s后退出. sleep(2); }
僵尸进程怎么产生?
$i_pid = pcntl_fork(); if (0 == $i_pid) { // 子进程10s后退出,变成僵尸进程 sleep(10); } else if ($i_pid > 0) { // 父进程休眠1000s后退出. sleep(1000); }
子进程怎么回收?
$i_pid = pcntl_fork(); if (0 == $i_pid) { // 在子进程中 for ($i = 1; $i <= 10; $i++) { sleep(1); echo "子进程PID " . posix_getpid() . "倒计时 : " . $i . PHP_EOL; } } else if ($i_pid > 0) { $i_ret = pcntl_wait($status); echo $i_ret . ' : ' . $status . PHP_EOL; // while保持父进程不退出 while (true) { sleep(1); } }
子进程怎么回收?非阻塞版本
<?php // fork出十个子进程 for ($i = 1; $i <= 10; $i++) { $i_pid = pcntl_fork(); // 每个子进程随机运行1-5秒钟 if (0 == $i_pid) { $i_rand_time = mt_rand(1, 5); sleep($i_rand_time); exit; } // 父进程收集所有子进程PID else if ($i_pid > 0) { } } while (true) { // sleep使父进程不会因while导致CPU爆炸. sleep(1); //设置WNOHANG参数不会阻塞,就是需要外层包个循环 $pid = pcntl_wait($status, WNOHANG); if ($pid == 0) { //目前还没有结束的子进程 continue; } if ($pid == -1) { //已经结束啦 很蓝的啦 exit("所有进程均已终止" . PHP_EOL); } // 如果子进程是正常结束 if (pcntl_wifexited($status)) { // 获取子进程结束时候的 返回错误码 $i_code = pcntl_wexitstatus($status); echo $pid . "正常结束,最终返回:" . $i_code . PHP_EOL; } // 如果子进程是被信号终止 if (pcntl_wifsignaled($status)) { // 获取是哪个信号终止的该进程 $i_signal = pcntl_wtermsig($status); echo $pid . "由信号结束,信号为:" . $i_signal . PHP_EOL; } // 如果子进程是[临时挂起] if (pcntl_wifstopped($status)) { // 获取是哪个信号让他挂起 $i_signal = pcntl_wstopsig($status); echo $pid . "被挂起,挂起信号为:" . $i_signal . PHP_EOL; } }
如何创建守护进程?
$pid = pcntl_fork(); if ($pid > 0) { //1)在父进程中执行fork并exit推出 exit(); } elseif ($pid == 0) { if (posix_setsid() < 0) { //2)在子进程中调用setsid函数创建新的会话 exit(); } chdir('/'); //3)在子进程中调用chdir函数,让根目录 ” / ” 成为子进程的工作目录 umask(0); //4)在子进程中调用umask函数,设置进程的umask为0 echo "create success, pid = " . posix_getpid(); //5)在子进程中关闭任何不需要的文件描述符 fclose(STDIN); fclose(STDOUT); fclose(STDERR); } //可以把上面封装成函数daemon(); while (true) {} //具体业务 如何修改进程名? for ($i = 1; $i <= 4; $i++) { $i_pid = pcntl_fork(); if (0 == $i_pid) { //子进程 cli_set_process_title("Worker Process"); //修改子进程的名字 while (true) { sleep(1); } } } cli_set_process_title("Master Process"); //修改父进程的名字 while (true) { sleep(1); }
进程怎么接收信号?
// 信号处理回调 function signal_handler($signal) { switch ($signal) { case SIGTERM: echo "sigterm信号." . PHP_EOL; break; case SIGUSR2: echo "sigusr2信号." . PHP_EOL; break; case SIGUSR1: echo "sigusr1信号." . PHP_EOL; break; default: echo "其他信号." . PHP_EOL; } } // 给进程安装3个信号处理回调 pcntl_signal(SIGTERM, "signal_handler"); pcntl_signal(SIGUSR1, "signal_handler"); pcntl_signal(SIGUSR2, "signal_handler"); while (true) { posix_kill(posix_getpid(), SIGUSR1);//发送一个信号给当前进程 posix_kill(posix_getpid(), SIGUSR1); pcntl_signal_dispatch(); //调一次分发一次信号,调用之前,信号累积在队列里 posix_kill(posix_getpid(), SIGUSR2); posix_kill(posix_getpid(), SIGUSR2); sleep(1); //稍微休息一下 }
其中第 1,2 行与第 3,4,5,6 行中间隔了一秒,体会一下 pcntl_signal_dispatch 这个函数
进程怎么接收信号 (不阻塞版本)?
//php7.1及以上才能用这个函数 pcntl_async_signals(true); // 信号处理回调 function signal_handler($signal) { switch ($signal) { case SIGTERM: echo "sigterm信号." . PHP_EOL; break; case SIGUSR2: echo "sigusr2信号." . PHP_EOL; break; case SIGUSR1: echo "sigusr1信号." . PHP_EOL; break; default: echo "其他信号." . PHP_EOL; } } // 给进程安装信号... pcntl_signal(SIGTERM, "signal_handler"); pcntl_signal(SIGUSR1, "signal_handler"); pcntl_signal(SIGUSR2, "signal_handler"); while (true) { posix_kill(posix_getpid(), SIGUSR1);//发送一个信号给当前进程 posix_kill(posix_getpid(), SIGUSR2); sleep(1); //稍微休息一下 }
进程怎么阻塞信号
pcntl_async_signals(true); // 信号处理回调 function signal_handler($signal) { switch ($signal) { case SIGTERM: echo "sigterm信号." . PHP_EOL; break; case SIGUSR2: echo "sigusr2信号." . PHP_EOL; break; case SIGUSR1: echo "sigusr1信号." . PHP_EOL; break; default: echo "其他信号." . PHP_EOL; } } // 给进程安装信号... pcntl_signal(SIGTERM, "signal_handler"); pcntl_signal(SIGUSR1, "signal_handler"); pcntl_signal(SIGUSR2, "signal_handler"); //把SIGUSR1阻塞,收到这个信号先不处理 pcntl_sigprocmask(SIG_BLOCK, [SIGUSR1], $a_oldset); $counter = 0; while (true) { posix_kill(posix_getpid(), SIGUSR1);//发送一个信号给当前进程 posix_kill(posix_getpid(), SIGUSR2); sleep(1); //稍微休息一下 if ($counter++ == 5) { //解除SIGUSR1信号阻塞,并立刻执行SIGUSR1处理回调函数 pcntl_sigprocmask(SIG_UNBLOCK, [SIGUSR1], $a_oldset); } }
以上がPHPマルチプロセス開発面接に関するよくある質問のまとめ(回答付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。