彙總有關PHP多程序開發面試常見問題(附答案)

2022-12-21 18:00:25
本篇文章給大家帶來了關於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 可執行檔案

  • REL 可重定位檔案,也稱為靜態庫檔案,連結器連結之後成為動態庫檔案,比如 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 協定實現的終端,比如用 SSH 進行的登入,或者 telnet, 那麼你將得到一個叫做 /dev/pts/X 的偽終端同時在

/proc/bash pid/fd 生成三個識別符號指向當前的 /dev/pts/X

0 標準輸入 滑鼠,鍵盤

1 標準輸出 顯示器

2 標準錯誤 顯示器

d74f2a0ebe2444fcb13d45ef7903c32.jpg

什麼是程序?

程序退出

  • 執行到最後一行語句

  • 執行時遇到 return 時

  • 執行時遇到 exit () 函數的時候

  • 程式異常的時候

  • 程序接收到中斷訊號

程序結束時並不會真的退出,還會駐留在內在中,pcntl_wait (pcntl_waitpid) 函數來獲取程序的終止狀態碼同時該函數會釋放終止程序的記憶體空間,如果不這麼做,會產生很多殭屍程序佔用大量的記憶體空間

孤兒程序

父程序執行完,子程序在執行,則子程序會被頭號程序 init 接管,這型別的程序成為孤兒程序

殭屍程序

子程序執行完,父程序沒有呼叫 pcntl_wait () 回收,程序狀態變成 Z+

守護行程

父程序是 init 程序,一般在系統啟動時開始執行,除非強行終止,否則直到系統關機都保持執行。守護行程經常以超級使用者(root)許可權執行,因為它們要使用特殊的埠(1-1024)或存取某些特殊的資源。

什麼是行程群組?

多個行程群組成一個行程群組,每個行程群組只有一個組長,組長的 PID 就是行程群組的 ID;組內所有程序退出時,行程群組才會消失,可以通過 ps -ajx 命令檢視 pgid

56f7e621b1ad05ee6147873e99ef561.jpg

什麼是對談?

多個行程群組組成一個對談,每個對談都有一個對談首程序。對談的特點

1) 使用 setsid () 函數可以建立一個新的對談

2) 對談首程序無法呼叫 setsid,會報錯

3) 非對談首程序程序可呼叫 setsid 建立出一個新的對談,這個行為會導致該程序會建立一個新的行程群組且自身為該行程群組組長,該程序會建立出一個新的對談組且自身為該對談組組長,該程序會脫離當前命令列控制終端

現實上的比喻就是除了老闆之後,員工都可以呼叫 我上我也行 () 這個函數變成老闆且不受原公司的控制

什麼是訊號?

訊號是程序間通訊的其中一種方式,平時用到的 kill -9 pid, 指的不是用第九種方式殺死程序,而是傳送訊號值為 9 的訊號給程序,而剛好訊號 9 是 SIGKILL, 功能是停止程序,檢視作業系統支援的訊號命令: kill -l

6230b142a76c129ef63a0fca2427997.jpg

一般使用 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
登入後複製

e17358e3ac92497fa4fc36718836b9b.jpg

說明父程序和三個子程序一共四個程序,實際上共用了一個 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);
}
登入後複製

dfd5bc530b4cb896f8986b116281c3a.jpg

程序怎麼接收訊號?

// 訊號處理回撥
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);   //稍微休息一下
}
登入後複製

5f3044794a8ff42161d22b7b0fba26f.jpg

其中第 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多程序開發面試常見問題(附答案)的詳細內容,更多請關注TW511.COM其它相關文章!