今天翻PHP源碼,無意中翻到了pcntl的源碼,簡單看了看,被嚇了一跳。
這是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;
}
在這個函數的第三句話做了一件事,把pcntl_signal_dispatch這個函數注冊成了tick的處理函數。而pcntl_signal_dispatch這個函數是做什么的呢?
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;
? ? ? ?}
? ? ? ?......
}
這個函數比較長,但核心的代碼就是上面這個循環。php把注冊的信號都放在一個queue里面,然后每次調用這個函數的時候,一個個來查看是否收到了信號需要處理,如果有信號的話,就調用相應的信號處理函數。
結合上面的初始化的代碼,可以推測出php的信號處理函數是基于ticks來實現的,而不是注冊到真正系統底層的信號處理函數中。而如果使用ticks的話,比如delare ticks=1, 那么每執行一條php語句都會調用上面的函數一次。而實際大部分時間里面并沒有信號需要處理,所以這會造成極大的浪費。
那么,實際是這樣嗎?
首先,我們先寫一個下面這樣的代碼。
然后,跑起來。
我們可以通過kill命令給進程發送信號。通過kill -l得知SIGUSR1是30。然后,我們就向這個進程發送信號了。
前面的圖其實已經給出結果了。我發送了兩次信號,程序打印了兩次30。
說明這種方式是沒問題的。
然后,我們注釋掉delare那行。
再次跑起來,同樣也是用kill來發送信號。
這次啥也沒收到了。
上面的這個實驗證明了php的信號處理確實是基于ticks的。那么,各種php寫的服務程序豈不是被這個拖累了?
我翻了翻Workerman的代碼,發現人家就處理得巧妙多了。它沒有declare ticks,而是直接在主循環里面調用pcntl_signal_dispatch函數。這樣就把pcntl_signal_dispatch的調用頻率下降了很多,而且還保證能達到近似實時的信號處理。
事實上,一般需要信號處理的代碼都是后端服務程序,而一般的后端服務程序都是按照事件處理的結構來編寫的,也就是說,這種程序里面必定會有個主事件循環。在事件循環的每次循環中主動調用pcntl_signal_dispatch,就能基本實時的把信號處理掉,而且還能保證一個比較好的性能。
當然,你以為拋棄ticks,直接在事件循環中調用pcntl_signal_dispatch就是信號處理的正確打開方式嗎?NO~NO~NO~,正確的編寫需要php處理信號功能的代碼,就應該去使用swoole,人家直接在c上面實現了信號處理,比pcntl不知道高到哪去了。