本篇內容介紹了“PHP多進程開發(fā)面試的常見問題怎么解決”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
目前創(chuàng)新互聯(lián)已為1000+的企業(yè)提供了網站建設、域名、網頁空間、網站托管、企業(yè)網站設計、長葛網站維護等服務,公司將堅持客戶導向、應用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
PHP多進程開發(fā)
先介紹一些簡單命令
echo $$ //輸出當前bash進程
strace -s 65500 -p 進程號 //打印進程系統(tǒng)調用
kill -s 10 pid //發(fā)送信號
kill -s SIGUSR2 pid //發(fā)送信號
pstree -ap //查看進程樹
ps -ajx //查看進程信息
ps 命令字段解析:
PPID:父進程ID
PID:進程ID
PGID:進程組ID
SID:會話ID
TTY:所在終端
STAT:進程狀態(tài) R運行 Z僵尸 S睡眠 T停止 D睡眠無法被喚醒
UID:unix用戶ID
COMMAND:啟動命令
什么是程序?
一般指可執(zhí)行文件,在 Linux 系統(tǒng)中它按 ELF 格式進行存儲,沒有后綴可言,file 命令可以查看 elf 文件的具體類型
ELF 全程 Executable Linkable Format 可執(zhí)行可鏈接格式
ELF 分為四大種類
EXEC 可執(zhí)行文件
REL 可重定位文件,也稱為靜態(tài)庫文件,鏈接器鏈接之后成為動態(tài)庫文件,比如 event.so sockets.so curl.so
Shared Object File 共享目標文件
core dump 存儲進程產生的異常信息
可通過 objdump/readelf 命令查看 ELF 文件相關信息
什么是終端?
tty 物理終端
tty 是最令人熟悉的了,在 Linux 中,/dev/ttyX 代表的都是上述的物理終端,其中,/dev/tty1~/dev/tty63 代表的是本地終端,也就是接到本機的鍵盤顯示器可以操作的終端
/dev/console 當前焦點終端
pts 偽終端
通過 tcp/ip 協(xié)議實現(xiàn)的終端,比如用 SSH 進行的登錄,或者 telnet, 那么你將得到一個叫做 /dev/pts/X 的偽終端同時在
/proc/bash pid/fd 生成三個標識符指向當前的 /dev/pts/X
0 標準輸入 鼠標,鍵盤
1 標準輸出 顯示器
2 標準錯誤 顯示器
什么是進程?
進程退出
運行到最后一行語句
運行時遇到 return 時
運行時遇到 exit () 函數的時候
程序異常的時候
進程接收到中斷信號
進程結束時并不會真的退出,還會駐留在內在中,pcntl_wait (pcntl_waitpid) 函數來獲取進程的終止狀態(tài)碼同時該函數會釋放終止進程的內存空間,如果不這么做,會產生很多僵尸進程占用大量的內存空間
孤兒進程
父進程運行完,子進程在運行,則子進程會被頭號進程 init 接管,這類型的進程成為孤兒進程
僵尸進程
子進程運行完,父進程沒有調用 pcntl_wait () 回收,進程狀態(tài)變成 Z+
守護進程
父進程是 init 進程,一般在系統(tǒng)啟動時開始運行,除非強行終止,否則直到系統(tǒng)關機都保持運行。守護進程經常以超級用戶(root)權限運行,因為它們要使用特殊的端口(1-1024)或訪問某些特殊的資源。
什么是進程組?
多個進程組成一個進程組,每個進程組只有一個組長,組長的 PID 就是進程組的 ID;組內所有進程退出時,進程組才會消失,可以通過 ps -ajx 命令查看 pgid
什么是會話?
多個進程組組成一個會話,每個會話都有一個會話首進程。會話的特點
1) 使用 setsid () 函數可以創(chuàng)建一個新的會話
2) 會話首進程無法調用 setsid,會報錯
3) 非會話首進程進程可調用 setsid 創(chuàng)建出一個新的會話,這個行為會導致該進程會創(chuàng)建一個新的進程組且自身為該進程組組長,該進程會創(chuàng)建出一個新的會話組且自身為該會話組組長,該進程會脫離當前命令行控制終端
現(xiàn)實上的比喻就是除了老板之后,員工都可以調用 我上我也行 () 這個函數變成老板且不受原公司的控制
什么是信號?
信號是進程間通信的其中一種方式,平時用到的 kill -9 pid, 指的不是用第九種方式殺死進程,而是發(fā)送信號值為 9 的信號給進程,而剛好信號 9 是 SIGKILL, 功能是停止進程,查看操作系統(tǒng)支持的信號命令: kill -l
一般使用 1-31, 注意看沒有 32,33 這兩個信號
信號的產生來源可能是:
鍵盤上按了 Ctrl+C 會產生 SIGINT 信號,關閉終端會產生 SIGHUP 信號
硬件也能產生信號
使用 kill 命令
軟件產生,比如在管道里當一側準備寫管道時可能會產生 SIGPIPE 信號
當一個進程收到一個信號時,三個可選操作
操作系統(tǒng)默認的方式,比如 SIGKILL 就是殺死進程
忽略掉這個信號,pcntl_signal (SIGKILL, SIG_IGN, false); 進程收到 SIGKILL 命令時將不為所動
有自己的想法,pcntl_signal (SIGKILL, function ($signal){// 自己的想法}, false); 這樣將會觸發(fā)自定義回調
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();//發(fā)送信號
pcntl 命令
//創(chuàng)建一個計時器,在指定的秒數后向進程發(fā)送一個SIGALRM信號。每次對 pcntl_alarm()的調用都會取消之前設置的alarm信號。如果seconds設置為0,將不會創(chuàng)建alarm信號。
pcntl_alarm(int $seconds);
//在當前進程當前位置產生子進程,子進程會復制父進程的代碼段和數據段(Copy on write 寫時復制,當子進程要修改內存空間時,操作系統(tǒng)會分配新的內存給子進程),ELF文件的結構,如果父進程先退出,子進程變成孤兒進程,被pid=1進程接管
pcntl_fork();
//安裝一個信號處理器
pcntl_signal(int $signo, callback $handler);
//調用等待信號的處理器,觸發(fā)全部未執(zhí)行的信號回調
pcntl_signal_dispatch()
//設置或檢索阻塞信號
pcntl_sigprocmask(int $how, array $set[, array &$oldset])
//等待或返回fork的子進程狀態(tài),wait函數掛起當前進程的執(zhí)行直到一個子進程退出或接收到一個信號要求中斷當前進程或調用一個信號處理函數。用此函數時已經退出(俗稱僵尸進程),此函數立刻返回。子進程使用的所有系統(tǒng)資源將被釋放。
pcntl_wait($status)
//加個WNOHANG參數,不掛起父進程,如果沒有子進程退出返回0,如果有子進程退出返回子進程pid,如果返回-1表示父進程已經沒有子進程
pcntl_wait($status, WNOHANG)
//基本同pcntl_wait,waitpid可以指定子進程id
pcntl_waitpid ($pid ,$status)
pcntl_waitpid ($pid ,$status, WNOHANG)
//檢查狀態(tài)代碼是否代表一個正常的退出。參數 status 是提供給成功調用 pcntl_waitpid() 時的狀態(tài)參數。
pcntl_wifexited($status)
//返回一個中斷的子進程的返回代碼 當php exit(10)時,這個函數返回10,這個函數僅在函數pcntl_wifexited()返回 TRUE.時有效
pcntl_wexitstatus($status)
//檢查子進程狀態(tài)碼是否代表由于某個信號而中斷。參數 status 是提供給成功調用 pcntl_waitpid() 時的狀態(tài)參數。
pcntl_wifsignaled($status)
//返回導致子進程中斷的信號
pcntl_wtermsig($status)
//檢查子進程當前是否已經停止,此函數只有作用于pcntl_wait使用了WUNTRACED作為 option的時候
pcntl_wifstopped($status)
//返回導致子進程停止的信號
pcntl_wstopsig($status)
//檢索由最后一個失敗的pcntl函數設置的錯誤數
pcntl_errno()
pcntl_get_last_error()
//檢索與給定errno關聯(lián)的系統(tǒng)錯誤消息
pcntl_strerror(pcntl_errno())
pcntl_fork () 執(zhí)行之前先與 redis 建立一個連接,然后再開 3 個子進程之后多少個 Redis 連接?
<?php
$o_redis = new Redis();
$o_redis->connect( '127.0.0.1', 6379 );
// 使用for循環(huán)搞出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 是一個單進程單線程的服務器,所以接收到的命令都是順序執(zhí)行順序返回的,所以當客戶端多個進程共享一個 redis 連接時,當有四個進程向 Redis 服務端發(fā)起請求,返回四個結果,誰先搶到就是誰的,正確的做法是每個子進程創(chuàng)建一個 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參數不會阻塞,就是需要外層包個循環(huán)
$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;
}
}
如何創(chuàng)建守護進程?
$pid = pcntl_fork();
if ($pid > 0) { //1)在父進程中執(zhí)行fork并exit推出
exit();
} elseif ($pid == 0) {
if (posix_setsid() < 0) { //2)在子進程中調用setsid函數創(chuàng)建新的會話
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) {} //具體業(yè)務
如何修改進程名?
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);//發(fā)送一個信號給當前進程
posix_kill(posix_getpid(), SIGUSR1);
pcntl_signal_dispatch(); //調一次分發(fā)一次信號,調用之前,信號累積在隊列里
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);//發(fā)送一個信號給當前進程
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);//發(fā)送一個信號給當前進程
posix_kill(posix_getpid(), SIGUSR2);
sleep(1); //稍微休息一下
if ($counter++ == 5) {
//解除SIGUSR1信號阻塞,并立刻執(zhí)行SIGUSR1處理回調函數
pcntl_sigprocmask(SIG_UNBLOCK, [SIGUSR1], $a_oldset);
}
}
“PHP多進程開發(fā)面試的常見問題怎么解決”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注創(chuàng)新互聯(lián)網站,小編將為大家輸出更多高質量的實用文章!
名稱欄目:PHP多進程開發(fā)面試的常見問題怎么解決
本文地址:http://chinadenli.net/article44/jgijee.html
成都網站建設公司_創(chuàng)新互聯(lián),為您提供網站內鏈、動態(tài)網站、品牌網站設計、企業(yè)建站、微信公眾號、網站制作
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)