您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“PHP多進(jìn)程開發(fā)面試的常見問題怎么解決”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
PHP多進(jìn)程開發(fā)
先介紹一些簡單命令
echo $$ //輸出當(dāng)前bash進(jìn)程
strace -s 65500 -p 進(jìn)程號(hào) //打印進(jìn)程系統(tǒng)調(diào)用
kill -s 10 pid //發(fā)送信號(hào)
kill -s SIGUSR2 pid //發(fā)送信號(hào)
pstree -ap //查看進(jìn)程樹
ps -ajx //查看進(jìn)程信息
ps 命令字段解析:
PPID:父進(jìn)程ID
PID:進(jìn)程ID
PGID:進(jìn)程組ID
SID:會(huì)話ID
TTY:所在終端
STAT:進(jìn)程狀態(tài) R運(yùn)行 Z僵尸 S睡眠 T停止 D睡眠無法被喚醒
UID:unix用戶ID
COMMAND:啟動(dòng)命令
什么是程序?
一般指可執(zhí)行文件,在 Linux 系統(tǒng)中它按 ELF 格式進(jìn)行存儲(chǔ),沒有后綴可言,file 命令可以查看 elf 文件的具體類型
ELF 全程 Executable Linkable Format 可執(zhí)行可鏈接格式
ELF 分為四大種類
EXEC 可執(zhí)行文件
REL 可重定位文件,也稱為靜態(tài)庫文件,鏈接器鏈接之后成為動(dòng)態(tài)庫文件,比如 event.so sockets.so curl.so
Shared Object File 共享目標(biāo)文件
core dump 存儲(chǔ)進(jìn)程產(chǎn)生的異常信息
可通過 objdump/readelf 命令查看 ELF 文件相關(guān)信息
什么是終端?
tty 物理終端
tty 是最令人熟悉的了,在 Linux 中,/dev/ttyX 代表的都是上述的物理終端,其中,/dev/tty1~/dev/tty63 代表的是本地終端,也就是接到本機(jī)的鍵盤顯示器可以操作的終端
/dev/console 當(dāng)前焦點(diǎn)終端
pts 偽終端
通過 tcp/ip 協(xié)議實(shí)現(xiàn)的終端,比如用 SSH 進(jìn)行的登錄,或者 telnet, 那么你將得到一個(gè)叫做 /dev/pts/X 的偽終端同時(shí)在
/proc/bash pid/fd 生成三個(gè)標(biāo)識(shí)符指向當(dāng)前的 /dev/pts/X
0 標(biāo)準(zhǔn)輸入 鼠標(biāo),鍵盤
1 標(biāo)準(zhǔn)輸出 顯示器
2 標(biāo)準(zhǔn)錯(cuò)誤 顯示器
什么是進(jìn)程?
進(jìn)程退出
運(yùn)行到最后一行語句
運(yùn)行時(shí)遇到 return 時(shí)
運(yùn)行時(shí)遇到 exit () 函數(shù)的時(shí)候
程序異常的時(shí)候
進(jìn)程接收到中斷信號(hào)
進(jìn)程結(jié)束時(shí)并不會(huì)真的退出,還會(huì)駐留在內(nèi)在中,pcntl_wait (pcntl_waitpid) 函數(shù)來獲取進(jìn)程的終止?fàn)顟B(tài)碼同時(shí)該函數(shù)會(huì)釋放終止進(jìn)程的內(nèi)存空間,如果不這么做,會(huì)產(chǎn)生很多僵尸進(jìn)程占用大量的內(nèi)存空間
孤兒進(jìn)程
父進(jìn)程運(yùn)行完,子進(jìn)程在運(yùn)行,則子進(jìn)程會(huì)被頭號(hào)進(jìn)程 init 接管,這類型的進(jìn)程成為孤兒進(jìn)程
僵尸進(jìn)程
子進(jìn)程運(yùn)行完,父進(jìn)程沒有調(diào)用 pcntl_wait () 回收,進(jìn)程狀態(tài)變成 Z+
守護(hù)進(jìn)程
父進(jìn)程是 init 進(jìn)程,一般在系統(tǒng)啟動(dòng)時(shí)開始運(yùn)行,除非強(qiáng)行終止,否則直到系統(tǒng)關(guān)機(jī)都保持運(yùn)行。守護(hù)進(jìn)程經(jīng)常以超級(jí)用戶(root)權(quán)限運(yùn)行,因?yàn)樗鼈円褂锰厥獾亩丝冢?-1024)或訪問某些特殊的資源。
什么是進(jìn)程組?
多個(gè)進(jìn)程組成一個(gè)進(jìn)程組,每個(gè)進(jìn)程組只有一個(gè)組長,組長的 PID 就是進(jìn)程組的 ID;組內(nèi)所有進(jìn)程退出時(shí),進(jìn)程組才會(huì)消失,可以通過 ps -ajx 命令查看 pgid
什么是會(huì)話?
多個(gè)進(jìn)程組組成一個(gè)會(huì)話,每個(gè)會(huì)話都有一個(gè)會(huì)話首進(jìn)程。會(huì)話的特點(diǎn)
1) 使用 setsid () 函數(shù)可以創(chuàng)建一個(gè)新的會(huì)話
2) 會(huì)話首進(jìn)程無法調(diào)用 setsid,會(huì)報(bào)錯(cuò)
3) 非會(huì)話首進(jìn)程進(jìn)程可調(diào)用 setsid 創(chuàng)建出一個(gè)新的會(huì)話,這個(gè)行為會(huì)導(dǎo)致該進(jìn)程會(huì)創(chuàng)建一個(gè)新的進(jìn)程組且自身為該進(jìn)程組組長,該進(jìn)程會(huì)創(chuàng)建出一個(gè)新的會(huì)話組且自身為該會(huì)話組組長,該進(jìn)程會(huì)脫離當(dāng)前命令行控制終端
現(xiàn)實(shí)上的比喻就是除了老板之后,員工都可以調(diào)用 我上我也行 () 這個(gè)函數(shù)變成老板且不受原公司的控制
什么是信號(hào)?
信號(hào)是進(jìn)程間通信的其中一種方式,平時(shí)用到的 kill -9 pid, 指的不是用第九種方式殺死進(jìn)程,而是發(fā)送信號(hào)值為 9 的信號(hào)給進(jìn)程,而剛好信號(hào) 9 是 SIGKILL, 功能是停止進(jìn)程,查看操作系統(tǒng)支持的信號(hào)命令: kill -l
一般使用 1-31, 注意看沒有 32,33 這兩個(gè)信號(hào)
信號(hào)的產(chǎn)生來源可能是:
鍵盤上按了 Ctrl+C 會(huì)產(chǎn)生 SIGINT 信號(hào),關(guān)閉終端會(huì)產(chǎn)生 SIGHUP 信號(hào)
硬件也能產(chǎn)生信號(hào)
使用 kill 命令
軟件產(chǎn)生,比如在管道里當(dāng)一側(cè)準(zhǔn)備寫管道時(shí)可能會(huì)產(chǎn)生 SIGPIPE 信號(hào)
當(dāng)一個(gè)進(jìn)程收到一個(gè)信號(hào)時(shí),三個(gè)可選操作
操作系統(tǒng)默認(rèn)的方式,比如 SIGKILL 就是殺死進(jìn)程
忽略掉這個(gè)信號(hào),pcntl_signal (SIGKILL, SIG_IGN, false); 進(jìn)程收到 SIGKILL 命令時(shí)將不為所動(dòng)
有自己的想法,pcntl_signal (SIGKILL, function ($signal){// 自己的想法}, false); 這樣將會(huì)觸發(fā)自定義回調(diào)
pcntl_signal () 信號(hào)處理器是會(huì)被子進(jìn)程繼承的,所以 fork () 之前最后先行處理信號(hào)處理器
posix 命令
//需要安裝posix擴(kuò)展
posix_getpid(); //獲取進(jìn)程ID
posix_getppid();//獲取父進(jìn)程ID
posix_getpgid(posix_getppid());//獲取進(jìn)程組ID
posix_getpgrp());//同上
posix_getsid(posix_getpid()));//獲取會(huì)話ID
posix_getuid();//獲取當(dāng)前登錄用戶UID
posix_getgid();//獲取當(dāng)前登錄用戶組UID
posix_geteuid();//獲取當(dāng)前有效用戶UID
posix_getguid();//獲取當(dāng)前有效用戶組UID
posix_kill();//發(fā)送信號(hào)
pcntl 命令
//創(chuàng)建一個(gè)計(jì)時(shí)器,在指定的秒數(shù)后向進(jìn)程發(fā)送一個(gè)SIGALRM信號(hào)。每次對(duì) pcntl_alarm()的調(diào)用都會(huì)取消之前設(shè)置的alarm信號(hào)。如果seconds設(shè)置為0,將不會(huì)創(chuàng)建alarm信號(hào)。
pcntl_alarm(int $seconds);
//在當(dāng)前進(jìn)程當(dāng)前位置產(chǎn)生子進(jìn)程,子進(jìn)程會(huì)復(fù)制父進(jìn)程的代碼段和數(shù)據(jù)段(Copy on write 寫時(shí)復(fù)制,當(dāng)子進(jìn)程要修改內(nèi)存空間時(shí),操作系統(tǒng)會(huì)分配新的內(nèi)存給子進(jìn)程),ELF文件的結(jié)構(gòu),如果父進(jìn)程先退出,子進(jìn)程變成孤兒進(jìn)程,被pid=1進(jìn)程接管
pcntl_fork();
//安裝一個(gè)信號(hào)處理器
pcntl_signal(int $signo, callback $handler);
//調(diào)用等待信號(hào)的處理器,觸發(fā)全部未執(zhí)行的信號(hào)回調(diào)
pcntl_signal_dispatch()
//設(shè)置或檢索阻塞信號(hào)
pcntl_sigprocmask(int $how, array $set[, array &$oldset])
//等待或返回fork的子進(jìn)程狀態(tài),wait函數(shù)掛起當(dāng)前進(jìn)程的執(zhí)行直到一個(gè)子進(jìn)程退出或接收到一個(gè)信號(hào)要求中斷當(dāng)前進(jìn)程或調(diào)用一個(gè)信號(hào)處理函數(shù)。用此函數(shù)時(shí)已經(jīng)退出(俗稱僵尸進(jìn)程),此函數(shù)立刻返回。子進(jìn)程使用的所有系統(tǒng)資源將被釋放。
pcntl_wait($status)
//加個(gè)WNOHANG參數(shù),不掛起父進(jìn)程,如果沒有子進(jìn)程退出返回0,如果有子進(jìn)程退出返回子進(jìn)程pid,如果返回-1表示父進(jìn)程已經(jīng)沒有子進(jìn)程
pcntl_wait($status, WNOHANG)
//基本同pcntl_wait,waitpid可以指定子進(jìn)程id
pcntl_waitpid ($pid ,$status)
pcntl_waitpid ($pid ,$status, WNOHANG)
//檢查狀態(tài)代碼是否代表一個(gè)正常的退出。參數(shù) status 是提供給成功調(diào)用 pcntl_waitpid() 時(shí)的狀態(tài)參數(shù)。
pcntl_wifexited($status)
//返回一個(gè)中斷的子進(jìn)程的返回代碼 當(dāng)php exit(10)時(shí),這個(gè)函數(shù)返回10,這個(gè)函數(shù)僅在函數(shù)pcntl_wifexited()返回 TRUE.時(shí)有效
pcntl_wexitstatus($status)
//檢查子進(jìn)程狀態(tài)碼是否代表由于某個(gè)信號(hào)而中斷。參數(shù) status 是提供給成功調(diào)用 pcntl_waitpid() 時(shí)的狀態(tài)參數(shù)。
pcntl_wifsignaled($status)
//返回導(dǎo)致子進(jìn)程中斷的信號(hào)
pcntl_wtermsig($status)
//檢查子進(jìn)程當(dāng)前是否已經(jīng)停止,此函數(shù)只有作用于pcntl_wait使用了WUNTRACED作為 option的時(shí)候
pcntl_wifstopped($status)
//返回導(dǎo)致子進(jìn)程停止的信號(hào)
pcntl_wstopsig($status)
//檢索由最后一個(gè)失敗的pcntl函數(shù)設(shè)置的錯(cuò)誤數(shù)
pcntl_errno()
pcntl_get_last_error()
//檢索與給定errno關(guān)聯(lián)的系統(tǒng)錯(cuò)誤消息
pcntl_strerror(pcntl_errno())
pcntl_fork () 執(zhí)行之前先與 Redis 建立一個(gè)連接,然后再開 3 個(gè)子進(jìn)程之后多少個(gè) Redis 連接?
<?php
$o_redis = new Redis();
$o_redis->connect( '127.0.0.1', 6379 );
// 使用for循環(huán)搞出3個(gè)子進(jìn)程來
for ( $i = 1; $i <= 3; $i++ ) {
$i_pid = pcntl_fork();
if ( 0 == $i_pid ) {
// 使用while保證三個(gè)子進(jìn)程不會(huì)退出...
while( true ) {
sleep( 1 );
}
}
}
// 使用while保證主進(jìn)程不會(huì)退出...
while( true ) {
sleep( 1 );
}
netstat -ant |grep 6379
說明父進(jìn)程和三個(gè)子進(jìn)程一共四個(gè)進(jìn)程,實(shí)際上共享了一個(gè) Redis 長連接
上面這種寫法會(huì)有什么問題?
因?yàn)?Redis 是一個(gè)單進(jìn)程單線程的服務(wù)器,所以接收到的命令都是順序執(zhí)行順序返回的,所以當(dāng)客戶端多個(gè)進(jìn)程共享一個(gè) redis 連接時(shí),當(dāng)有四個(gè)進(jìn)程向 Redis 服務(wù)端發(fā)起請(qǐng)求,返回四個(gè)結(jié)果,誰先搶到就是誰的,正確的做法是每個(gè)子進(jìn)程創(chuàng)建一個(gè) Redis 連接,或者用連接池
孤兒進(jìn)程怎么產(chǎn)生?
$i_pid = pcntl_fork();
if (0 == $i_pid) {
// 子進(jìn)程10秒鐘后退出.
for ($i = 1; $i <= 10; $i++) {
sleep(1);
echo "我的父進(jìn)程是:" . posix_getppid() . PHP_EOL;
}
} else if ($i_pid > 0) {
// 父進(jìn)程休眠2s后退出.
sleep(2);
}
僵尸進(jìn)程怎么產(chǎn)生?
$i_pid = pcntl_fork();
if (0 == $i_pid) {
// 子進(jìn)程10s后退出,變成僵尸進(jìn)程
sleep(10);
} else if ($i_pid > 0) {
// 父進(jìn)程休眠1000s后退出.
sleep(1000);
}
子進(jìn)程怎么回收?
$i_pid = pcntl_fork();
if (0 == $i_pid) {
// 在子進(jìn)程中
for ($i = 1; $i <= 10; $i++) {
sleep(1);
echo "子進(jìn)程PID " . posix_getpid() . "倒計(jì)時(shí) : " . $i . PHP_EOL;
}
} else if ($i_pid > 0) {
$i_ret = pcntl_wait($status);
echo $i_ret . ' : ' . $status . PHP_EOL;
// while保持父進(jìn)程不退出
while (true) {
sleep(1);
}
}
子進(jìn)程怎么回收?非阻塞版本
<?php
// fork出十個(gè)子進(jìn)程
for ($i = 1; $i <= 10; $i++) {
$i_pid = pcntl_fork();
// 每個(gè)子進(jìn)程隨機(jī)運(yùn)行1-5秒鐘
if (0 == $i_pid) {
$i_rand_time = mt_rand(1, 5);
sleep($i_rand_time);
exit;
} // 父進(jìn)程收集所有子進(jìn)程PID
else if ($i_pid > 0) {
}
}
while (true) {
// sleep使父進(jìn)程不會(huì)因while導(dǎo)致CPU爆炸.
sleep(1);
//設(shè)置WNOHANG參數(shù)不會(huì)阻塞,就是需要外層包個(gè)循環(huán)
$pid = pcntl_wait($status, WNOHANG);
if ($pid == 0) { //目前還沒有結(jié)束的子進(jìn)程
continue;
}
if ($pid == -1) { //已經(jīng)結(jié)束啦 很藍(lán)的啦
exit("所有進(jìn)程均已終止" . PHP_EOL);
}
// 如果子進(jìn)程是正常結(jié)束
if (pcntl_wifexited($status)) {
// 獲取子進(jìn)程結(jié)束時(shí)候的 返回錯(cuò)誤碼
$i_code = pcntl_wexitstatus($status);
echo $pid . "正常結(jié)束,最終返回:" . $i_code . PHP_EOL;
}
// 如果子進(jìn)程是被信號(hào)終止
if (pcntl_wifsignaled($status)) {
// 獲取是哪個(gè)信號(hào)終止的該進(jìn)程
$i_signal = pcntl_wtermsig($status);
echo $pid . "由信號(hào)結(jié)束,信號(hào)為:" . $i_signal . PHP_EOL;
}
// 如果子進(jìn)程是[臨時(shí)掛起]
if (pcntl_wifstopped($status)) {
// 獲取是哪個(gè)信號(hào)讓他掛起
$i_signal = pcntl_wstopsig($status);
echo $pid . "被掛起,掛起信號(hào)為:" . $i_signal . PHP_EOL;
}
}
如何創(chuàng)建守護(hù)進(jìn)程?
$pid = pcntl_fork();
if ($pid > 0) { //1)在父進(jìn)程中執(zhí)行fork并exit推出
exit();
} elseif ($pid == 0) {
if (posix_setsid() < 0) { //2)在子進(jìn)程中調(diào)用setsid函數(shù)創(chuàng)建新的會(huì)話
exit();
}
chdir('/'); //3)在子進(jìn)程中調(diào)用chdir函數(shù),讓根目錄 ” / ” 成為子進(jìn)程的工作目錄
umask(0); //4)在子進(jìn)程中調(diào)用umask函數(shù),設(shè)置進(jìn)程的umask為0
echo "create success, pid = " . posix_getpid();
//5)在子進(jìn)程中關(guān)閉任何不需要的文件描述符
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
}
//可以把上面封裝成函數(shù)daemon();
while (true) {} //具體業(yè)務(wù)
如何修改進(jìn)程名?
for ($i = 1; $i <= 4; $i++) {
$i_pid = pcntl_fork();
if (0 == $i_pid) { //子進(jìn)程
cli_set_process_title("Worker Process"); //修改子進(jìn)程的名字
while (true) {
sleep(1);
}
}
}
cli_set_process_title("Master Process"); //修改父進(jìn)程的名字
while (true) {
sleep(1);
}
進(jìn)程怎么接收信號(hào)?
// 信號(hào)處理回調(diào)
function signal_handler($signal)
{
switch ($signal) {
case SIGTERM:
echo "sigterm信號(hào)." . PHP_EOL;
break;
case SIGUSR2:
echo "sigusr2信號(hào)." . PHP_EOL;
break;
case SIGUSR1:
echo "sigusr1信號(hào)." . PHP_EOL;
break;
default:
echo "其他信號(hào)." . PHP_EOL;
}
}
// 給進(jìn)程安裝3個(gè)信號(hào)處理回調(diào)
pcntl_signal(SIGTERM, "signal_handler");
pcntl_signal(SIGUSR1, "signal_handler");
pcntl_signal(SIGUSR2, "signal_handler");
while (true) {
posix_kill(posix_getpid(), SIGUSR1);//發(fā)送一個(gè)信號(hào)給當(dāng)前進(jìn)程
posix_kill(posix_getpid(), SIGUSR1);
pcntl_signal_dispatch(); //調(diào)一次分發(fā)一次信號(hào),調(diào)用之前,信號(hào)累積在隊(duì)列里
posix_kill(posix_getpid(), SIGUSR2);
posix_kill(posix_getpid(), SIGUSR2);
sleep(1); //稍微休息一下
}
其中第 1,2 行與第 3,4,5,6 行中間隔了一秒,體會(huì)一下 pcntl_signal_dispatch 這個(gè)函數(shù)
進(jìn)程怎么接收信號(hào) (不阻塞版本)?
//php7.1及以上才能用這個(gè)函數(shù)
pcntl_async_signals(true);
// 信號(hào)處理回調(diào)
function signal_handler($signal)
{
switch ($signal) {
case SIGTERM:
echo "sigterm信號(hào)." . PHP_EOL;
break;
case SIGUSR2:
echo "sigusr2信號(hào)." . PHP_EOL;
break;
case SIGUSR1:
echo "sigusr1信號(hào)." . PHP_EOL;
break;
default:
echo "其他信號(hào)." . PHP_EOL;
}
}
// 給進(jìn)程安裝信號(hào)...
pcntl_signal(SIGTERM, "signal_handler");
pcntl_signal(SIGUSR1, "signal_handler");
pcntl_signal(SIGUSR2, "signal_handler");
while (true) {
posix_kill(posix_getpid(), SIGUSR1);//發(fā)送一個(gè)信號(hào)給當(dāng)前進(jìn)程
posix_kill(posix_getpid(), SIGUSR2);
sleep(1); //稍微休息一下
}
進(jìn)程怎么阻塞信號(hào)
pcntl_async_signals(true);
// 信號(hào)處理回調(diào)
function signal_handler($signal)
{
switch ($signal) {
case SIGTERM:
echo "sigterm信號(hào)." . PHP_EOL;
break;
case SIGUSR2:
echo "sigusr2信號(hào)." . PHP_EOL;
break;
case SIGUSR1:
echo "sigusr1信號(hào)." . PHP_EOL;
break;
default:
echo "其他信號(hào)." . PHP_EOL;
}
}
// 給進(jìn)程安裝信號(hào)...
pcntl_signal(SIGTERM, "signal_handler");
pcntl_signal(SIGUSR1, "signal_handler");
pcntl_signal(SIGUSR2, "signal_handler");
//把SIGUSR1阻塞,收到這個(gè)信號(hào)先不處理
pcntl_sigprocmask(SIG_BLOCK, [SIGUSR1], $a_oldset);
$counter = 0;
while (true) {
posix_kill(posix_getpid(), SIGUSR1);//發(fā)送一個(gè)信號(hào)給當(dāng)前進(jìn)程
posix_kill(posix_getpid(), SIGUSR2);
sleep(1); //稍微休息一下
if ($counter++ == 5) {
//解除SIGUSR1信號(hào)阻塞,并立刻執(zhí)行SIGUSR1處理回調(diào)函數(shù)
pcntl_sigprocmask(SIG_UNBLOCK, [SIGUSR1], $a_oldset);
}
}
“PHP多進(jìn)程開發(fā)面試的常見問題怎么解決”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。