今天翻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不知道高到哪去了。