你知道PHP信號(hào)處理的正確打開方式嗎?

今天翻PHP源碼,無(wú)意中翻到了pcntl的源碼,簡(jiǎn)單看了看,被嚇了一跳。

這是pcntl模塊初始化的代碼。

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);

? ? ? ?return SUCCESS;

}

在這個(gè)函數(shù)的第三句話做了一件事,把pcntl_signal_dispatch這個(gè)函數(shù)注冊(cè)成了tick的處理函數(shù)。而pcntl_signal_dispatch這個(gè)函數(shù)是做什么的呢?

void pcntl_signal_dispatch()

{

? ? ? ?......

? ? ? ?/* Allocate */

? ? ? ?while (queue) {

? ? ? ? ? ? ? ?if (zend_hash_index_find(&PCNTL_G(php_signal_table), queue->signo, (void **) &handle)==SUCCESS) {

? ? ? ? ? ? ? ? ? ? ? ?MAKE_STD_ZVAL(retval);

? ? ? ? ? ? ? ? ? ? ? ?MAKE_STD_ZVAL(param);

? ? ? ? ? ? ? ? ? ? ? ?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, ?m TSRMLS_CC);

? ? ? ? ? ? ? ? ? ? ? ?zval_ptr_dtor(?m);

? ? ? ? ? ? ? ? ? ? ? ?zval_ptr_dtor(&retval);

? ? ? ? ? ? ? ?}

? ? ? ? ? ? ? ?next = queue->next;

? ? ? ? ? ? ? ?queue->next = PCNTL_G(spares);

? ? ? ? ? ? ? ?PCNTL_G(spares) = queue;

? ? ? ? ? ? ? ?queue = next;

? ? ? ?}

? ? ? ?......

}

這個(gè)函數(shù)比較長(zhǎng),但核心的代碼就是上面這個(gè)循環(huán)。php把注冊(cè)的信號(hào)都放在一個(gè)queue里面,然后每次調(diào)用這個(gè)函數(shù)的時(shí)候,一個(gè)個(gè)來(lái)查看是否收到了信號(hào)需要處理,如果有信號(hào)的話,就調(diào)用相應(yīng)的信號(hào)處理函數(shù)。

結(jié)合上面的初始化的代碼,可以推測(cè)出php的信號(hào)處理函數(shù)是基于ticks來(lái)實(shí)現(xiàn)的,而不是注冊(cè)到真正系統(tǒng)底層的信號(hào)處理函數(shù)中。而如果使用ticks的話,比如delare ticks=1, 那么每執(zhí)行一條php語(yǔ)句都會(huì)調(diào)用上面的函數(shù)一次。而實(shí)際大部分時(shí)間里面并沒(méi)有信號(hào)需要處理,所以這會(huì)造成極大的浪費(fèi)。

那么,實(shí)際是這樣嗎?

首先,我們先寫一個(gè)下面這樣的代碼。

然后,跑起來(lái)。

我們可以通過(guò)kill命令給進(jìn)程發(fā)送信號(hào)。通過(guò)kill -l得知SIGUSR1是30。然后,我們就向這個(gè)進(jìn)程發(fā)送信號(hào)了。

前面的圖其實(shí)已經(jīng)給出結(jié)果了。我發(fā)送了兩次信號(hào),程序打印了兩次30。

說(shuō)明這種方式是沒(méi)問(wèn)題的。

然后,我們注釋掉delare那行。

再次跑起來(lái),同樣也是用kill來(lái)發(fā)送信號(hào)。

這次啥也沒(méi)收到了。

上面的這個(gè)實(shí)驗(yàn)證明了php的信號(hào)處理確實(shí)是基于ticks的。那么,各種php寫的服務(wù)程序豈不是被這個(gè)拖累了?

我翻了翻Workerman的代碼,發(fā)現(xiàn)人家就處理得巧妙多了。它沒(méi)有declare ticks,而是直接在主循環(huán)里面調(diào)用pcntl_signal_dispatch函數(shù)。這樣就把pcntl_signal_dispatch的調(diào)用頻率下降了很多,而且還保證能達(dá)到近似實(shí)時(shí)的信號(hào)處理。

事實(shí)上,一般需要信號(hào)處理的代碼都是后端服務(wù)程序,而一般的后端服務(wù)程序都是按照事件處理的結(jié)構(gòu)來(lái)編寫的,也就是說(shuō),這種程序里面必定會(huì)有個(gè)主事件循環(huán)。在事件循環(huán)的每次循環(huán)中主動(dòng)調(diào)用pcntl_signal_dispatch,就能基本實(shí)時(shí)的把信號(hào)處理掉,而且還能保證一個(gè)比較好的性能。

當(dāng)然,你以為拋棄ticks,直接在事件循環(huán)中調(diào)用pcntl_signal_dispatch就是信號(hào)處理的正確打開方式嗎?NO~NO~NO~,正確的編寫需要php處理信號(hào)功能的代碼,就應(yīng)該去使用swoole,人家直接在c上面實(shí)現(xiàn)了信號(hào)處理,比pcntl不知道高到哪去了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容