溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

PHP進(jìn)程信號(hào)如何處理

發(fā)布時(shí)間:2023-04-14 10:51:54 來源:億速云 閱讀:96 作者:iii 欄目:編程語言

這篇文章主要介紹“PHP進(jìn)程信號(hào)如何處理”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“PHP進(jìn)程信號(hào)如何處理”文章能幫助大家解決問題。

一、在Linux操作系統(tǒng)中有哪些信號(hào)

1、簡單介紹信號(hào)

信號(hào)是事件發(fā)生時(shí)對(duì)進(jìn)程的通知機(jī)制,有時(shí)又稱為軟件中斷。一個(gè)進(jìn)程可以向另一個(gè)進(jìn)程發(fā)送信號(hào),比如子進(jìn)程結(jié)束時(shí)都會(huì)向父進(jìn)程發(fā)送一個(gè)SIGCHLD(17號(hào)信號(hào))來通知父進(jìn)程,所以有時(shí)信號(hào)也被當(dāng)作一種進(jìn)程間通信的機(jī)制。

在linux系統(tǒng)下,通常我們使用 kill -9 XXPID來結(jié)束一個(gè)進(jìn)程,其實(shí)這個(gè)命令的實(shí)質(zhì)就是向某進(jìn)程發(fā)送SIGKILL(9號(hào)信號(hào)),對(duì)于在前臺(tái)進(jìn)程我們通常用Ctrl+c快捷鍵來結(jié)束運(yùn)行,該快捷鍵的實(shí)質(zhì)是向當(dāng)前進(jìn)程發(fā)送SIGINT(2號(hào)信號(hào)),而進(jìn)程收到該信號(hào)的默認(rèn)行為是結(jié)束運(yùn)行

2、常用信號(hào)

下邊這些信號(hào),可以使用kill -l命令進(jìn)行查看PHP進(jìn)程信號(hào)如何處理

下邊介紹幾個(gè)比較重要且常用的信號(hào):

信號(hào)名信號(hào)值信號(hào)類型信號(hào)描述
SIGHUP1終止進(jìn)程(終端線路掛斷)本信號(hào)在用戶終端連接(正?;蚍钦?、結(jié)束時(shí)發(fā)出, 通常是在終端的控制進(jìn)程結(jié)束時(shí), 通知同一session內(nèi)的各個(gè)作業(yè), 這時(shí)它們與控制終端不再關(guān)聯(lián)
SIGQUIT2終止進(jìn)程(中斷進(jìn)程)程序終止(interrupt、信號(hào), 在用戶鍵入INTR字符(通常是Ctrl-C、時(shí)發(fā)出
SIGQUIT3建立CORE文件終止進(jìn)程,并且生成CORE文件進(jìn)程,并且生成CORE文件 SIGQUIT 和SIGINT類似, 但由QUIT字符(通常是Ctrl-、來控制. 進(jìn)程在因收到SIGQUIT退出時(shí)會(huì)產(chǎn)生core文件, 在這個(gè)意義上類似于一個(gè)程序錯(cuò)誤信 號(hào)
SIGFPE8建立CORE文件(浮點(diǎn)異常)SIGFPE 在發(fā)生致命的算術(shù)運(yùn)算錯(cuò)誤時(shí)發(fā)出. 不僅包括浮點(diǎn)運(yùn)算錯(cuò)誤, 還包括溢 出及除數(shù)為0等其它所有的算術(shù)的錯(cuò)誤
SIGKILL9終止進(jìn)程(殺死進(jìn)程)SIGKILL 用來立即結(jié)束程序的運(yùn)行. 本信號(hào)不能被阻塞, 處理和忽略
SIGSEGV11
SIGSEGV 試圖訪問未分配給自己的內(nèi)存, 或試圖往沒有寫權(quán)限的內(nèi)存地址寫數(shù)據(jù)
SIGALRM14終止進(jìn)程(計(jì)時(shí)器到時(shí))SIGALRM 時(shí)鐘定時(shí)信號(hào), 計(jì)算的是實(shí)際的時(shí)間或時(shí)鐘時(shí)間. alarm函數(shù)使用該信號(hào)
SIGTERM15終止進(jìn)程(軟件終止信號(hào))SIGTERM 程序結(jié)束(terminate、信號(hào), 與SIGKILL不同的是該信號(hào)可以被阻塞和處理. 通常用來要求程序自己正常退出. shell命令kill缺省產(chǎn)生這個(gè)信號(hào)
SIGCHLD17忽略信號(hào)(當(dāng)子進(jìn)程停止或退出時(shí)通知父進(jìn)程)SIGCHLD 子進(jìn)程結(jié)束時(shí), 父進(jìn)程會(huì)收到這個(gè)信號(hào)
SIGVTALRM26終止進(jìn)程(虛擬計(jì)時(shí)器到時(shí))SIGVTALRM 虛擬時(shí)鐘信號(hào). 類似于SIGALRM, 但是計(jì)算的是該進(jìn)程占用的CPU時(shí)間
SIGIO29忽略信號(hào)(描述符上可以進(jìn)行I/O)SIGIO 文件描述符準(zhǔn)備就緒, 可以開始進(jìn)行輸入/輸出操作

二、PHP中處理信號(hào)相關(guān)函數(shù)

PHP的pcntl擴(kuò)展以及posix擴(kuò)展為我們提供了若干操作信號(hào)的方法(若想使用這些函數(shù),需要先安裝這幾個(gè)擴(kuò)展)

下邊具體介紹幾個(gè)我在本次任務(wù)中用到的方法:

declare

declare結(jié)構(gòu)用來設(shè)定一段代碼的執(zhí)行指令。declare的語法和其它流程控制結(jié)構(gòu)相似

declare (directive)
   statement

directive部分允許設(shè)定declare代碼段的行為。目前只認(rèn)識(shí)兩個(gè)指令:ticks和encoding。declare代碼段中的 statement部分將被執(zhí)行——怎樣執(zhí)行以及執(zhí)行中有什么副作用出現(xiàn)取決于directive中設(shè)定的指令

Ticks

Tick(時(shí)鐘周期)是一個(gè)在declare代碼段中解釋器每執(zhí)行N條可計(jì)時(shí)的低級(jí)語句就會(huì)發(fā)生的事件N的值是在declare 中的directive部分用ticks=N來指定的。不是所有語句都可計(jì)時(shí)。通常條件表達(dá)式參數(shù)表達(dá)式都不可計(jì)時(shí)。在每個(gè)tick中出現(xiàn)的事件是由register_tick_function()來指定的,注意每個(gè) tick 中可以出現(xiàn)多個(gè)事件 更詳細(xì)的內(nèi)容。

<?php
declare(ticks=1);//每執(zhí)行一條時(shí),觸發(fā)register_tick_function()注冊(cè)的函數(shù)
$a=1;//在注冊(cè)之前,不算
function test(){//定義一個(gè)函數(shù)
   echo "執(zhí)行\(zhòng)n";
}
register_tick_function('test');//該條注冊(cè)函數(shù)會(huì)被當(dāng)成低級(jí)語句被執(zhí)行
for($i=0;$i<=2;$i++){//for算一條低級(jí)語句
   $i=$i;//賦值算一條
}
輸出:六個(gè)“執(zhí)行”
pcntl_signal

pcntl_signal,安裝一個(gè)信號(hào)處理器

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

函數(shù)pcntl_signal()為signo指定的信號(hào)安裝一個(gè)新的信號(hào)處理器

declare(ticks = 1);
pcntl_signal(SIGINT,function(){
   echo "你按了Ctrl+C".PHP_EOL;
});
while(1){
   sleep(1);//死循環(huán)運(yùn)行低級(jí)語句
}
輸出:當(dāng)按Ctrl+C之后,會(huì)輸出“你按了Ctrl+C”
posix_kill

posix_kill,向進(jìn)程發(fā)送一個(gè)信號(hào)

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

第一個(gè)參數(shù)為進(jìn)程ID,第二個(gè)參數(shù)為你要發(fā)送的信號(hào)

a.php
<?php
declare(ticks = 1);
echo getmypid();//獲取當(dāng)前進(jìn)程id
pcntl_signal(SIGINT,function(){
   echo "你給我發(fā)了SIGINT信號(hào)";
});
while(1){
   sleep(1);
}

b.php
<?php
posix_kill(執(zhí)行1.php時(shí)輸出的進(jìn)程id, SIGINT);
pcntl_signal_dispatch

pcntl_signal_dispatch,調(diào)用等待信號(hào)的處理器

pcntl_signal_dispatch ( void ) : bool

函數(shù)pcntl_signal_dispatch()調(diào)用每個(gè)等待信號(hào)通過pcntl_signal()安裝的處理器

<?php
echo "安裝信號(hào)處理器...\n";
pcntl_signal(SIGHUP,  function($signo) {
    echo "信號(hào)處理器被調(diào)用\n";
});
echo "為自己生成SIGHUP信號(hào)...\n";
posix_kill(posix_getpid(), SIGHUP);
echo "分發(fā)...\n";
pcntl_signal_dispatch();
echo "完成\n";
?>

輸出:
安裝信號(hào)處理器...
為自己生成SIGHUP信號(hào)...
分發(fā)...
信號(hào)處理器被調(diào)用
完成
pcntl_async_signals()

異步信號(hào)處理,用于啟用無需 ticks (這會(huì)帶來很多額外的開銷)的異步信號(hào)處理。(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中處理信號(hào)量的方式

前邊我們知道我們可以通過declare(ticks=1)和pcntl_signal組合的方式監(jiān)聽信號(hào),即每一條PHP低級(jí)語句,就會(huì)檢查一次當(dāng)前進(jìn)程是否有未處理的信號(hào),這其實(shí)是十分耗性能的。

pcntl_signal的實(shí)現(xiàn)原理是,觸發(fā)信號(hào)后先將信號(hào)加入一個(gè)隊(duì)列中。然后在PHP的ticks回調(diào)函數(shù)中不斷檢查是否有信號(hào),如果有信號(hào)就執(zhí)行PHP中指定的回調(diào)函數(shù),如果沒有則跳出函數(shù)。

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函數(shù)。這個(gè)時(shí)候?qū)⒉辉谛枰猟eclare,只需要在循環(huán)中增加該函數(shù),就可以調(diào)用信號(hào)通過了:

<?php
echo getmypid();//獲取當(dāng)前進(jìn)程id
pcntl_signal(SIGUSR1,function(){
   echo "觸發(fā)信號(hào)用戶自定義信號(hào)1";
});
while(1){
   pcntl_signal_dispatch();
   sleep(1);//死循環(huán)運(yùn)行低級(jí)語句
}

大家都知道PHP的ticks=1表示每執(zhí)行1行PHP代碼就回調(diào)此函數(shù)。實(shí)際上大部分時(shí)間都沒有信號(hào)產(chǎn)生,但ticks的函數(shù)一直會(huì)執(zhí)行。如果一個(gè)服務(wù)器程序1秒中接收1000次請(qǐng)求,平均每個(gè)請(qǐng)求要執(zhí)行1000行PHP代碼。那么PHP的pcntl_signal,就帶來了額外的 1000 * 1000,也就是100萬次空的函數(shù)調(diào)用。這樣會(huì)浪費(fèi)大量的CPU資源。比較好的做法是去掉ticks,轉(zhuǎn)而使用pcntl_signal_dispatch,在代碼循環(huán)中自行處理信號(hào)。 pcntl_signal_dispatch 函數(shù)的實(shí)現(xiàn):

void pcntl_signal_dispatch()
{
//.... 這里略去一部分代碼,queue即是信號(hào)隊(duì)列
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;
}
}

但是上邊這種,也有個(gè)惡心的地方就是,它得放在死循環(huán)中。PHP7.1之后出來了一個(gè)完成異步的信號(hào)接收并處理的函數(shù): pcntl_async_signals

<?php
//a.php
echo getmypid();
pcntl_async_signals(true);//開啟異步監(jiān)聽信號(hào)
pcntl_signal(SIGUSR1,function(){
   echo "觸發(fā)信號(hào)";
   posix_kill(getmypid(),SIGSTOP);
});
posix_kill(getmypid(),SIGSTOP);//給進(jìn)程發(fā)送暫停信號(hào)

//b.php
posix_kill(文件1進(jìn)程, SIGCONT);//給進(jìn)程發(fā)送繼續(xù)信號(hào)
posix_kill(文件1進(jìn)程, SIGUSR1);//給進(jìn)程發(fā)送user1信號(hào)

通過pcntl_async_signals方法,就不用再寫死循環(huán)了。

監(jiān)聽信號(hào)的包:

https://github.com/Rain-Life/monitorSignal

關(guān)于“PHP進(jìn)程信號(hào)如何處理”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。

向AI問一下細(xì)節(jié)

免責(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)容。

php
AI