本篇文章為大家帶來了關於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 全程Executable Linkable Format 可執行可連結格式
ELF 分為四大種類
EXEC 執行檔
php影片教學】
什麼是終端機?
什麼是進程?
進程退出
#孤兒程序##父進程運行完,子程序在運行,子程序會被頭號程序init 接管,這類型的程序成為孤兒程序
殭屍程序##子程序運行完,父程序沒有呼叫pcntl_wait () 回收,進程狀態變成Z
守護程式
父進程是init 進程,一般在系統啟動時開始運行,除非強行終止,否則直到系統關機都保持運作。守護程式經常以超級使用者(root)權限執行,因為它們要使用特殊的連接埠(1-1024)或存取某些特殊的資源。
什麼是進程組?
多個進程組成一個進程組,每個進程組只有一個組長,組長的PID 就是進程組的ID;組內所有進程退出時,進程組才會消失,可以透過ps -ajx 指令查看pgid
什麼是會話?
多個進程組組成一個會話,每個會話都有一個會話首進程。會話的特性
1) 使用setsid () 函數可以建立一個新的會話2) 會話首程序無法呼叫setsid,會報錯##3) 非會話首進程進程可呼叫setsid 建立一個新的會話,這個行為會導致該進程會建立一個新的進程組且自身為該進程組組長,該進程會建立一個新的會話組且自身為該會話組組長,該行程會脫離目前命令列控制終端機
現實上的比喻就是除了老闆之後,員工都可以呼叫我上我也行() 這個函數變成老闆且不受原公司的控制
什麼是訊號?訊號是進程間通訊的其中一種方式,平常用到的kill -9 pid, 指的不是用第九種方式殺死進程,而是發送訊號值為9 的訊號給進程,而剛好訊號9 是SIGKILL, 功能是停止進程,查看作業系統支援的訊號指令: kill -l
一般使用1-31, 注意看沒有32,33 這兩個訊號訊號的產生來源可能是:
键盘上按了 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中文網其他相關文章!