首頁  >  文章  >  後端開發  >  一文徹底搞懂PHP進程訊號處理

一文徹底搞懂PHP進程訊號處理

藏色散人
藏色散人轉載
2023-04-01 07:30:032033瀏覽

這篇文章為大家帶來了關於PHP的相關知識,其中主要詳細介紹了PHP 進程信號處理,有興趣的朋友下面一起來看一下吧,希望對大家有幫助。

一文徹底搞懂PHP進程訊號處理

背景

#前兩週老大給我安排了一個任務,寫一個監聽訊號的包包。因為我司的專案是運行在容器裡邊的,每次上線,需要重新打包鏡像,然後啟動。在重新打包之前,Dokcer會先給容器發送一個訊號,然後等待一段逾時時間(預設10s)後,再發送SIGKILL訊號來終止容器

現在有一個情況,容器中有一個常駐進程,該常駐進程的任務是不斷的消費隊列裡的消息。假設現在要上線,需要關掉掉容器,Docker給容器裡跑的常駐進程發送一個信號,告訴它我10s後會將你關閉,假設現在已經過了9秒,常駐進程剛從隊列中取出一則訊息,1s內還沒將後續邏輯執行完,進程就已經被殺了,此時這條訊息就遺失了,且可能會產生髒數據

上邊就是這次任務的背景,需要透過監聽訊號來決定後續如何操作。對於上邊這種情況,當常駐進程收到Docker發送的關閉訊號時,將該進程阻塞即可,一直sleep,直到殺死容器。 OK,清楚背景之後,下邊就介紹一下PHP中的信號(後邊會再整理一篇這個包如何寫,並將包發佈到https://packagist.org/,供需要的小伙伴使用)【推薦學習:PHP影片教學

一、Linux作業系統中有哪些訊號

1、簡單介紹訊號

訊號是事件發生時對流程的通知機制,有時又稱為軟體中斷。一個行程可以向另一個行程發送訊號,例如子行程結束時都會向父行程發送一個SIGCHLD(17號訊號)來通知父行程,所以有時訊號也被當作一種行程間通訊的機制。

在linux系統下,通常我們使用kill -9 XXPID來結束一個進程,其實這個命令的實質就是向某進程發送SIGKILL(9號信號),對於在前台進程我們通常用Ctrl c快捷鍵來結束運行,該快捷鍵的實質是向當前進程發送SIGINT(2號信號),而進程收到該信號的預設行為是結束運行

##2 、常用訊號

下邊這些訊號,可以使用kill -l指令來檢視

下邊介紹幾個比較重要且常用的訊號:

# #建立CORE檔案(浮點異常)SIGFPE 在發生致命的算術運算錯誤時發出. 不僅包括浮點運算錯誤, 還包括溢出及除數為0等其它所有的算術的錯誤SIGKILL9終止進程(殺死進程)SIGKILL 用來立即結束程式的運作. 本訊號不能被阻塞, 處理與忽略SIGSEGV11SIGSEGV 試圖存取未分配給自己的記憶體, 或試圖在沒有寫權限的記憶體位址寫資料SIGALRM#14#終止進程(計時器到時)# SIGALRM 時脈定時訊號, 計算的是實際的時間或時脈時間. alarm函數使用此訊號SIGTERM15#終止進程(軟體終止訊號)SIGTERM 程式結束(terminate、訊號, 與SIGKILL不同的是該訊號可以被阻塞和處理. 通常用來要求程式本身正常退出.shell指令kill預設產生這個訊號SIGCHLD17忽略訊號(當子程序停止或退出時通知父程序)SIGCHLD 子程序結束時, 父進程會收到這個訊號SIGVTALRM26終止進程(虛擬計時器到時)SIGVTALRM 虛擬時鐘訊號. 類似SIGALRM, 但計算的是該行程佔用的CPU時間SIGIO29忽略訊號(描述符上可以進行I/O)SIGIO 檔案描述子準備就緒, 可以開始進行輸入/輸出操作#

二、PHP中處理訊號相關函數

#PHP的pcntl擴充以及posix 擴充為我們提供了若干操作訊號的方法(若想使用這些函數,需要先安裝這幾個擴充功能)

下邊具體介紹幾個我在本次任務中用到的方法:

declare

#declare結構用來設定一段程式碼的執行指令。 declare的語法和其它流程控制結構相似

declare (directive)
    statement

directive部分允許設定declare程式碼段的行為。目前只認識兩個指示:ticks和encoding。 declare程式碼段中的statement部分將會被執行-怎麼執行以及執行中有什麼副作用出現取決於directive中設定的指令

##Ticks

Tick(時脈週期)是一個在declare程式碼段中解釋器每執行N條可計時的

低階語句就會發生的事件N的值是在declare 中的directive部分用ticks=N來指定的。不是所有語句都可計時。通常條件表達式參數表達式都不可計時。在每個tick中出現的事件是由register_tick_function()來指定的,注意每個 tick 中可以出現多個事件 更詳細的內容,可查看官方文件:https://www.php.net/manual/zh/control-structures.declare.php

<?php
declare(ticks=1);//每执行一条时,触发register_tick_function()注册的函数
$a=1;//在注册之前,不算
function test(){//定义一个函数
    echo "执行\n";
}
register_tick_function(&#39;test&#39;);//该条注册函数会被当成低级语句被执行
for($i=0;$i<=2;$i++){//for算一条低级语句
    $i=$i;//赋值算一条
}
输出:六个“执行”

pcntl_signal

pcntl_signal,安裝一個訊號處理器

pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls = true ] ) : bool

函數pcntl_signal()為signo指定的訊號安裝一個新的訊號處理器

declare(ticks = 1);
pcntl_signal(SIGINT,function(){
    echo "你按了Ctrl+C".PHP_EOL;
});
while(1){
    sleep(1);//死循环运行低级语句
}
输出:当按Ctrl+C之后,会输出“你按了Ctrl+C”

posix_kill

posix_kill,向行程發送一個訊號

posix_kill ( int $pid , int $sig ) : bool

第一個參數為行程ID,第二個參數為你要傳送的訊號

a.php
<?php
declare(ticks = 1);
echo getmypid();//获取当前进程id
pcntl_signal(SIGINT,function(){
    echo "你给我发了SIGINT信号";
});
while(1){
    sleep(1);
}

b.php
<?php
posix_kill(执行1.php时输出的进程id, SIGINT);

pcntl_signal_dispatch

pcntl_signal_dispatch,呼叫等待訊號的處理器

pcntl_signal_dispatch ( void ) : bool

函數pcntl_signal_dispatch()呼叫每個等待訊號通過pcntl_signal()安裝的處理器

<?php
echo "安装信号处理器...\n";
pcntl_signal(SIGHUP,  function($signo) {
     echo "信号处理器被调用\n";
});
echo "为自己生成SIGHUP信号...\n";
posix_kill(posix_getpid(), SIGHUP);
echo "分发...\n";
pcntl_signal_dispatch();
echo "完成\n";
?>

输出:
安装信号处理器...
为自己生成SIGHUP信号...
分发...
信号处理器被调用
完成

pcntl_async_signals()

######################################################################################################### #####非同步訊號處理,用於啟用無需ticks (這會帶來許多額外的開銷)的非同步訊號處理。 (PHP>=7.1)###
<?php
pcntl_async_signals(true); // turn on async signals

pcntl_signal(SIGHUP,  function($sig) {
    echo "SIGHUP\n";
});

posix_kill(posix_getpid(), SIGHUP);

输出:
SIGHUP
############三、PHP中處理訊號量的方式##############前邊我們知道我們可以透過declare(ticks=1)和pcntl_signal組合的方式監聽訊號,也就是每一條PHP低階語句,就會檢查一次目前的進程是否有未處理訊號,這其實是十分耗能的。 ######pcntl_signal的實作原理是,觸發訊號後先將訊號加入一個佇列。然後在PHP的ticks回呼函數中不斷檢查是否有訊號,如果有訊號就執行PHP中指定的回呼函數,如果沒有則跳出函數。 ###
PHP_MINIT_FUNCTION(pcntl)
{
 php_register_signal_constants(INIT_FUNC_ARGS_PASSTHRU);
 php_pcntl_register_errno_constants(INIT_FUNC_ARGS_PASSTHRU);
 php_add_tick_function(pcntl_signal_dispatch TSRMLS_CC);

 return SUCCESS;
}
###在PHP5.3之後,有了pcntl_signal_dispatch函數。這時候將不在需要declare,只需要在循環中增加該函數,就可以調用信號通過了:###
<?php
echo getmypid();//获取当前进程id
pcntl_signal(SIGUSR1,function(){
    echo "触发信号用户自定义信号1";
});
while(1){
    pcntl_signal_dispatch();
    sleep(1);//死循环运行低级语句
}
###大家都知道PHP的ticks=1表示每執行1行PHP程式碼就回調此函數。實際上大部分時間都沒有訊號產生,但ticks的函數一直會執行。如果一個伺服器程式1秒中接收1000次請求,平均每個請求要執行1000行PHP代碼。那麼PHP的pcntl_signal,就帶來了額外的 1000 * 1000,也就是100萬次空的函數呼叫。這樣會浪費大量的CPU資源。比較好的做法是去掉ticks,改用pcntl_signal_dispatch,在程式碼循環中自行處理訊號。 pcntl_signal_dispatch 函數的實作:###
void pcntl_signal_dispatch()
{
 //.... 这里略去一部分代码,queue即是信号队列
 while (queue) {
  if ((handle = zend_hash_index_find(&PCNTL_G(php_signal_table), queue->signo)) != NULL) {
   ZVAL_NULL(&retval);
   ZVAL_LONG(&param, queue->signo);

   /* Call php signal handler - Note that we do not report errors, and we ignore the return value */
   /* FIXME: this is probably broken when multiple signals are handled in this while loop (retval) */
   call_user_function(EG(function_table), NULL, handle, &retval, 1, &param TSRMLS_CC);
   zval_ptr_dtor(&param);
   zval_ptr_dtor(&retval);
  }
  next = queue->next;
  queue->next = PCNTL_G(spares);
  PCNTL_G(spares) = queue;
  queue = next;
 }
}
###但是上邊這種,也有個噁心的地方就是,它得放在死循環中。 PHP7.1之後出來了一個完成非同步的訊號接收並處理的函數: pcntl_async_signals###
<?php
//a.php
echo getmypid();
pcntl_async_signals(true);//开启异步监听信号
pcntl_signal(SIGUSR1,function(){
    echo "触发信号";
    posix_kill(getmypid(),SIGSTOP);
});
posix_kill(getmypid(),SIGSTOP);//给进程发送暂停信号

//b.php
posix_kill(文件1进程, SIGCONT);//给进程发送继续信号
posix_kill(文件1进程, SIGUSR1);//给进程发送user1信号
###透過pcntl_async_signals方法,就不用再寫死循環了。 ###

监听信号的包:

https://github.com/Rain-Life/monitorSignal
訊號名稱 訊號值 訊號類型 訊號說明
SIGHUP 1 終止進程(終端線路掛斷) 本訊號在用戶終端連接(正常或非正常、結束時發出,通常是在終端機的控製程序結束時, 通知同一session內的各個作業, 這時它們與控制終端不再關聯
SIGQUIT 2 終止程序(中斷程序) 程式終止(interrupt、訊號, 在使用者鍵入INTR字元(通常是Ctrl-C、時發出
SIGQUIT 3 建立CORE檔案終止進程,並且產生CORE檔案 進程,並且產生CORE檔案 SIGQUIT 和SIGINT類似, 但由QUIT字元(通常是Ctrl-、來控制. 進程在收到SIGQUIT退出時會產生core檔, 在這個意義上類似於程式錯誤訊號
SIGFPE #8

以上是一文徹底搞懂PHP進程訊號處理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除