Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Обработка pcntl-сигналов в PHP

Anna | 31.05.2014 | нет комментариев

Про обработку сигналов в PHP теснее было написано несколько статей. Но там эта тема описана лишь неявно.

Сразу оговорюсь, что я знаю, что теснее вышел PHP 5.5 и 5.2 теснее нравственно устарел, но задачу необходимо было решать именно на PHP 5.2. Для тех везунчиков, кто использует больше новую версию PHP я тоже напишу, но ближе к концу статьи.

Обработка pcntl-сигналов в PHP делается путем передачи функции-обработчика в функцию pcntl_signal(), при этом на всякий сигнал дозволено повесить только один обработчик. Всякий дальнейший обработчик будет заменять предшествующий, при этом никаких нотайсов не будет.

Т.е. если вы используете несколько сторонних библитек и всякой необходимо обрабатывать какой-то сигнал, то сигнал будет обрабатывать только одна библиотека. Получается сторонние библиотеки обязаны только предоставлять калбеки для обработки сигналов, а пользователь библиотеки должен в обработчике сигналов вызывать эти калбеки.

Приведу пример:
Возможен в 2-х сторонних библиотеках применяются две различные библиотеки для логирования и обе библиотеки обязаны обрабатывать сигнал SIGHUP для того, что бы переоткрыть файл логов при его ротейте. Происходит это дальнейшим образом. Демон, тот, что отвечает за ротейт логов мувит файлы логов и посылает сигнал SIGHUP каждому процессам, которые применяют данный лог-файл. Позже перемещения файла лога, но до обработки сигнала процесс продолжает писать в тот же файл. Позже верной обработки этого сигнала процесс должен начать писать в новейший файл.
Обработчик сигнала не должен относиться к одной из этих библиотек, а должен являться частью приложения. В нем обязаны вызываться обработчики сигнала самих библиотек.

declare(ticks = 1)

До версии 5.3, что бы обработчик сигнала вызывался, необходимо непременно применять конструкциюdeclare(ticks = 1). Обыкновенного php-программиста такая конструкция вводит недоумение. При прочтении мануала становится не крепко внятнее, как она работает, исключительно для тех, кто не кодил мастерски на C и других языках, в которых есть конструкции управления исполнением:

Конструкция declare может быть использована в всеобщей области видимости, влияя на каждый дальнейший за ней код (впрочем если файл с declare был включен, тогда он не имеет воздействия на родительский файл).

Здесь очевидно необходимо рассматривать примеры.

Эффективность

Как эта конструкция влияет на продуктивность не написано, следственно я сделал бенчмарки. Файлы:
Example.php:

<?php
class Example {
	public function run() {
		for($i = 0; $i < 10000000; $i  );
	}
}

testWithTicksSpeed.php:

<?php
declare(ticks = 1);
require_once __DIR__ . '/Example.php';
$example = new Example();
$example->run();

testWithoutTicksSpeed.php:

<?php
require_once __DIR__ . '/Example.php';
$example = new Example();
$example->run();

Сразу скажу, что тест синтетический, т.к. в нем нет обращений к БД, чтение файлов и т.д. на эффективность которых declare(ticks = 1) никак не влияет.
Итоги:

mougrim@mougrim-pc:pcntls-signals$ time php testWithTicksSpeed.php
complete, process time: 11

real	0m10.186s
user	0m4.448s
sys	0m5.732s

mougrim@mougrim-pc:pcntls-signals$ time php testWithoutTicksSpeed.php
complete, process time: 2

real	0m1.515s
user	0m1.504s
sys	0m0.008s

Разница ~6,7 раз. В настоящем плане разница безусловно будет поменьше, но не хотелось бы терять источники и время там, где отлавливать сигналы не необходимо.

Что бы осознать, как съэкономить источники (не неизменно вызывать declare) необходимо осознать, как данный declare работает. В ходе эксперементов выяснилось следующее.
Данный код работает:

class Test_Signal
{
	// никогда не вызывается
	public function declareTicks()
	{
		echo "declare ticksn";
		declare(ticks = 1);
	}

	public function run()
	{
		// зарегистировать хендлеры через pcntl_signal
		// запустить стержневой цикл приложения
	}
}
$test = new Test_Signal();
$test->run();

Данный код не работает:

class Test_Signal
{
	public function run()
	{
		$this->declareTicks();
		// зарегистировать хендлеры через pcntl_signal
		// запустить стержневой цикл приложения
	}

	public function declareTicks()
	{
		echo "declare ticksn";
		declare(ticks = 1);
	}
}
$test = new Test_Signal();
$test->run();

Т.е. если для declare не указан какой-то определенный блок кода, то она действует для каждого кода, дальнейшего за ней. И здесь имеется ввиду код, обрабатываемый интерпретатором и превращаемый в OP-код.
Что бы было внятнее, разглядим пример из бенчмарка.
В классе Example сигналы обрабатываются:

declare(ticks = 1);
require_once __DIR__ . '/Example.php';
$example = new Example();
$example->run();

В классе Example сигналы не обрабатываются:

require_once __DIR__ . '/Example.php';
declare(ticks = 1);
$example = new Example();
$example->run();

Тонкости

При тестировании обработчиков сигналов обнаружились необычные вещи. В какой-то момент демон начинал много раз обрабатывать один и тот же сигнал так, что основновной код скрипта фактически не выполнялся. При единичной посылке сигнала SIGTERM, демон так же стал его обрабатывать много раз. Позже общения со знающими людьми выяснилось, что обработчик сигналов должен быть как дозволено поменьше и не выделять память. Это связанно с тем, что обработчик сигнала может быть вызван во время аллоцирования памяти и аллокация памяти в обработчике может привести к её повреждению и непредстказуемым итогам. Получается обработчик сигналов при применении declare(ticks = 1) должен быть минимален, скажем проставлять какой-то флаг, а непосредственная обработка должна быть в основном цикле скрипта.

Проверить это у меня не хватает познаний, т.к. на C/C не разрабатываю, но при применении Mougrim_Pcntl_SignalHandler, описанного ниже и тот, что не выделяет память во время обработки сигнала, эта задача огромнее не воспроизводилась.

Обработка сигналов в PHP 5.3

В PHP 5.3 возникла восхитительная функция pcntl_signal_dispatch(). Суть в том, что если не объявить declare(ticks = 1), то сигналы копятся в очередь и если вызвать функцию pcntl_signal_dispatch(), то вызовутся обработчики собранных сигналов. Если один и тот же сигнал был послан несколько раз, то обработчик тоже вызовится несколько раз. Эта функция решает задачи с продуктивностью и с минимизацией обработчика сигнала, т.к. обработка происходит не в любом месте, а только во время вызова pcntl_signal_dispatch().

SignalHandler

Пример обработчика сигналов для 5.2, файл src/lt5.3/Mougrim/Pcntl/SignalHandler.php:

<?php
declare(ticks = 1);

/**
 * @author Mougrim <rinat@mougrim.ru>
 */
class Mougrim_Pcntl_SignalHandler
{
	/**
	 * @var callable[]
	 */
	private $handlers = array();
	private $toDispatch = array();

	/**
	 * Добавление обработчика сигнала
	 *
	 * @param int       $signalNumber   номер сигнала, скажем SIGTERM
	 * @param callable  $handler        функция-обработчик игнала $signalNumber
	 * @param bool      $isAdd          если true, то заменить нынешние обработчики
	 */
	public function addHandler($signalNumber, $handler, $isAdd = true)
	{
		$isHandlerNotAttached = empty($this->handlers[$signalNumber]);
		if($isAdd)
			$this->handlers[$signalNumber][] = $handler;
		else
			$this->handlers[$signalNumber] = array($handler);

		if($isHandlerNotAttached && function_exists('pcntl_signal'))
		{
			$this->toDispatch[$signalNumber] = false;
			pcntl_signal($signalNumber, array($this, 'handleSignal'));
		}
	}

	/**
	 * Обработать собранные сигналы
	 */
	public function dispatch()
	{
		foreach($this->toDispatch as $signalNumber => $isNeedDispatch)
		{
			if(!$isNeedDispatch)
				continue;
			$this->toDispatch[$signalNumber] = false;
			foreach($this->handlers[$signalNumber] as $handler)
				call_user_func($handler, $signalNumber);
		}
	}

	/**
	 * Поставнока обработки сигнала в очередь
	 *
	 * @param int $signalNumber номер сигнала, скажем SIGTERM
	 */
	public function handleSignal($signalNumber)
	{
		$this->toDispatch[$signalNumber] = true;
	}
}

Обработчик решает две задачи:
1) он эмулирует pcntl_signal_dispatch();
2) разрешает применять несколько функций-обработчиков для одного сигнала.

Пример обработчика сигналов для 5.3 и выше, файл src/gte5.3/Mougrim/Pcntl/SignalHandler.php:

<?php
namespace MougrimPcntl;

/**
 * @package MougrimPcntl
 * @author Mougrim <rinat@mougrim.ru>
 */
class SignalHandler
{
	/**
	 * @var callable[]
	 */
	private $handlers = array();
	private $toDispatch = array();

	/**
	 * Добавление обработчика сигнала
	 *
	 * @param int       $signalNumber   номер сигнала, скажем SIGTERM
	 * @param callable  $handler        функция-обработчик игнала $signalNumber
	 * @param bool      $isAdd          если true, то заменить нынешние обработчики
	 */
	public function addHandler($signalNumber, $handler, $isAdd = true)
	{
		$isHandlerNotAttached = empty($this->handlers[$signalNumber]);
		if($isAdd)
			$this->handlers[$signalNumber][] = $handler;
		else
			$this->handlers[$signalNumber] = array($handler);

		if($isHandlerNotAttached && function_exists('pcntl_signal'))
		{
			$this->toDispatch[$signalNumber] = false;
			pcntl_signal($signalNumber, array($this, 'handleSignal'));
		}
	}

	/**
	 * Обработать собранные сигналы
	 */
	public function dispatch()
	{
		pcntl_signal_dispatch();
		foreach($this->toDispatch as $signalNumber => $isNeedDispatch)
		{
			if(!$isNeedDispatch)
				continue;
			$this->toDispatch[$signalNumber] = false;
			foreach($this->handlers[$signalNumber] as $handler)
				call_user_func($handler, $signalNumber);
		}
	}

	/**
	 * Поставнока обработки сигнала в очередь
	 *
	 * @param int $signalNumber номер сигнала, скажем SIGTERM
	 */
	private function handleSignal($signalNumber)
	{
		$this->toDispatch[$signalNumber] = true;
	}
}

Данный обработчик решает только одну задачу — он разрешает применять несколько функций-обработчиков сигналов. При этом интерфейс класса одинаков интерфейсу класса Mougrim_Pcntl_SignalHandler.

Пример применения

На последок пример применения, файлы:
signalExampleRun.php:

<?php
// в начале подключаем SignalHandler, что бы был вызван declare(ticks = 1);
require_once dirname(__FILE__) . "/src/lt5.3/Mougrim/Pcntl/SignalHandler.php";
require_once dirname(__FILE__) . "/SignalExample.php";;
$signalHandler = new Mougrim_Pcntl_SignalHandler();
$signalExample = new SignalExample($signalHandler);
$signalExample->run();

SignalExample.php:

<?php
class SignalExample
{
	private $signalHandler;

	public function __construct(Mougrim_Pcntl_SignalHandler $signalHandler)
	{
		$this->signalHandler = $signalHandler;
	}

	public function run()
	{
		// добавляем обработчик сигнала SIGTERM
		$this->signalHandler->addHandler(SIGTERM, array($this, 'terminate'));
		// добавляем обработчик сигнала SIGINT
		$this->signalHandler->addHandler(SIGINT, array($this, 'terminate'));

		while(true)
		{
			$this->signalHandler->dispatch();

			// итерация цикла
			echo "итерация циклаn";
			usleep(300000);
		}
	}

	public function terminate()
	{
		// послать SIGTERM детям
		// ...
		echo "terminaten";

		exit(0);
	}
}

Для 5.3 и выше пример аналогичен, только необходимо подключить src/gte5.3/Mougrim/Pcntl/SignalHandler.php и применять класс MougrimPcntlSignalHandler.

Итоги

1) Если вы используете PHP 5.3 либо выше и хотите избежать неявных задач, не используйте конструкцию declare(ticks = 1);
2) declare(ticks = 1); работает самостоятельно от условных конструкций и вызовов функций и работает в том коде, тот, что был «загружен» в интерпретатор позже объявления declare(ticks = 1);
3) Если применять Mougrim_Pcntl_SignalHandler в PHP 5.2, то он должен подключаться до файла с классом либо кодом с основным циклом программы, в котором необходимо обрабатывать сигналы;
4) Т.к. с declare(ticks = 1) приложение работает медленне, следственно объявлять эту конструкцию необходимо только там, где есть обработка сигналов.

Кому увлекательно, начальные коды классов MougrimPcntlSignalHandler и Mougrim_Pcntl_SignalHandler приведены на гитхабе.

*UPD.* В первом комментарии PsychodelEKS подсказывает, что даже если обработчик сигнала вызывается при вызове pcntl_signal_dispatch(), он все равно является обработчиком и если из под него запустить программу, скажем через system(), то запущенная программа не сумеет обрабатывать сигналы, т.к. сама будет являться обработчиком сигнала. Следственно я немножко изменю код класса MougrimPcntlSignalHandler, что бы обработчики вызывались отдельно, как это сделано в Mougrim_Pcntl_SignalHandler.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB