首页 > PHP系统编程, Swoole扩展, 进程间通信 > PHP官方的pcntl_signal性能极差

PHP官方的pcntl_signal性能极差

很多纯PHP开发的后端框架中都使用了pcntl扩展提供的信号处理函数pcntl_signal,实际上这个函数的性能是很差的。首先看一段示例代码:

declare(ticks = 1);
pcntl_signal(SIGINT, 'signalHandler');

这段代码在执行pcntl_signal前,先加入了declare(ticks = 1)。因为PHP的函数无法直接注册到操作系统信号设置中,所以pcntl信号需要依赖tick机制。通过查看pcntl.c的源码实现发现。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;
}

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

这样就存在一个比较严重的性能问题,大家都知道PHP的ticks=1表示每执行1行PHP代码就回调此函数。实际上大部分时间都没有信号产生,但ticks的函数一直会执行。如果一个服务器程序1秒中接收1000次请求,平均每个请求要执行1000行PHP代码。那么PHP的pcntl_signal,就带来了额外的 1000 * 1000,也就是100万次空的函数调用。这样会浪费大量的CPU资源。

比较好的做法是去掉ticks,转而使用pcntl_signal_dispatch,在代码循环中自行处理信号。而swoole中因为底层是C实现的,信号处理不受PHP的影响。swoole使用了目前Linux系统中最先进的signalfd来处理信号,几乎是没有任何额外消耗的。