Изрядное количество изменилось для расширения pthreads с выпуском pthreads v3. Эта статья призвана охватить необходимую информацию для тех, кто хочет обновить свои приложения с pthreads v2 до v3.
Если вы не знакомы с pthreads, проверьте мое введение в pthreads !
Большое спасибо Джо Уоткинсу за корректуру и помощь в улучшении моей статьи!
Общие изменения
В pthreads v3 было внесено несколько общих изменений. Первое и, пожалуй, самое заметное, это то, что pthreads нельзя использовать ни в какой среде, кроме интерфейса командной строки. Он никогда не предназначался для использования в среде веб-сервера (т. Е. В процессе FCGI) из-за проблем безопасности и масштабирования, поэтому теперь совет от pthreads v2 был реализован.
Также произошли некоторые изменения в рабочих. Ранее необходимо было отслеживать рабочие объекты, переданные рабочим, иначе, если они были уничтожены до того, как были выполнены рабочим потоком, возникла ошибка сегментации. Это было хорошо известным поведением, которое было кратко продемонстрировано в многопоточности в PHP со списком pthreads со следующим фрагментом:
class W extends Worker { public function run(){} } class S extends Stackable { public function run(){} } /* 1 */ $w = new W(); /* 2 */ $j = array( new S(), new S(), new S() ); /* 3 */ foreach ($j as $job) $w->stack($job); /* 4 */ $j = array(); $w->start(); $w->shutdown();
Это больше не проблема, потому что сами рабочие теперь отслеживают сложенные рабочие объекты.
Кроме того, в pthreads v3 произошли некоторые изменения в значении модификаторов методов. В pthreads v2 модификаторы методов имели особое значение в контексте потоковых объектов. В частности, защищенные методы имели неявный синхронизированный доступ (позволяющий безопасно выполнять их в нескольких контекстах), а частные методы могли выполняться только в контексте, к которому они привязаны. Эти отличающиеся семантики были теперь удалены из-за проблем надежности.
Например, возьмем следующий фрагмент:
class ExampleThread extends Thread { public $value = 0; public function run() { $this->exclusive(); } protected function exclusive() { for ($i = 0; $i < 10000; ++$i) { ++$this->value; } } } class Test extends ExampleThread { public function callExclusive() { $this->exclusive(); } }; $thread = new Test(); $thread->start(); $thread->callExclusive(); $thread->join(); var_dump($thread->value);
В pthreads v2 вызов ExampleThread::exclusive
из основного и нового контекста потока был безопасным. Выходное значение в конце скрипта всегда будет int(20000)
. Но в pthreads v3 это значение может быть любым от 1 до 20000 из-за условий состязания между двумя несинхронизированными циклами for
.
Чтобы добиться точно такого же поведения в pthreads v3, мы должны явно синхронизировать доступ, используя встроенный метод Threaded::synchronized
. Это нужно применять только к телу метода ExampleThread::exclusive
:
protected function exclusive() { $this->synchronized(function () { for ($i = 0; $i < 10000; ++$i) { ++$this->value; } }); }
Что касается удаления семантики модификатора приватного метода, это только сняло предыдущее ограничение. Таким образом, код, использующий это поведение, не должен изменяться.
Удаленные Классы
Классы Mutex
и Cond
были удалены. Это связано с тем, что их функциональность не нужна из-за функций синхронизации, уже предоставленных классом Threaded
. Использование взаимных исключающих блокировок и условий в PHP-коде также никогда не было особенно безопасным, поскольку взаимные блокировки могли легко возникнуть из ошибочного кода.
Класс Collectable
который расширил Threaded
, также был удален. Теперь у нас есть интерфейс Collectable
который реализован с помощью Threaded
. Интерфейс обеспечивает isGarbage
метод isGarbage
. Метод setGarbage
больше не нужен, потому что pthreads автоматически обрабатывает, когда задача должна считаться мусором (когда задача завершена). Класс Threaded
реализует метод Threaded::isGarbage
по умолчанию, который следует использовать в подавляющем большинстве случаев. Реализация по умолчанию всегда возвращает true
, так как любая задача в очереди задач является мусором (задача не может быть собрана перед выполнением). Только в редких случаях может потребоваться пользовательская реализация, поэтому переопределение метода Threaded::isGarbage
должно быть редкостью.
Ниже приведен краткий пример использования встроенного сборщика мусора в pthreads:
$worker = new Worker(); for ($i = 0; $i < 10; ++$i) { $worker->stack(new class extends Threaded {}); } $worker->start(); while ($worker->collect()); // blocks until all tasks have finished executing and have been collected $worker->shutdown();
Наконец, класс Stackable
, ранее связанный с классом Threaded
, был удален. Любые классы, которые расширили Stackable
теперь должны быть изменены для расширения Threaded
.
Удаленные Методы
Следующие методы были удалены:
-
Threaded::getTerminatedInfo
— из-за того, что сериализировать исключения небезопасно Встроенных альтернатив не существует, но поскольку PHP 7 преобразовал подавляющее большинство фатальных ошибок в исключения, вместо них можно использовать обработчики универсальных исключений:$task = new class extends Thread { public function run() { try { $this->task(); } catch (Throwable $ex) { // handle error here var_dump($ex->getMessage()); } } private function task() { $this->data = new Threaded(); $this->data = new StdClass(); // RuntimeException thrown var_dump(1); // never reached } }; $task->start() && $task->join();
(См. Ниже о новом добавлении класса
Volatile
и впоследствии, почему приведенный выше код ошибочен.) -
Threaded::from
— поскольку в PHP 7 есть анонимные классы, которые гораздо предпочтительнее использовать. -
Threaded::isWaiting
— потому что он просто не нужен при синхронизации. Поток не должен задаваться вопросом, ожидает ли он чего-то, и, как таковой, нет альтернатив этому методу. -
Threaded::lock
и его аналогThreaded::unlock
— по тем же причинам классыMutex
иCond
были удалены. Учитывая, что синхронизация теперь синхронизирует таблицу свойствThreaded
объектов, ее следует использовать вместо этого. -
Thread::kill
— из-за того, что это не безопасно для выполнения. Альтернатив нет — код не должен просто убивать поток в такой высокоуровневой среде. -
Thread::detach
— из-за того, что это не безопасно. Альтернатив нет — любой код, основанный на этом, необходимо будет переписать. -
Worker::isWorking
— потому что в этом нет необходимости. Чтобы увидеть, остались ли у работника какие-либо задачи, следует использовать методWorker::getStacked
, который будет возвращать размер оставшегося стека.
Измененные Методы
Следующие методы были изменены:
-
Worker::unstack
— он больше не принимает параметр (который ранее удалял переданную задачу из стека). Это означает, что по умолчанию теперь просто удаляется только первая задача (самая старая) из стека, а не удаляются все задачи из стека. -
Pool::collect
— теперь возвращает количество задач, которые нужно собрать, и колбэк-коллектор теперь необязательный. Если обратный вызов коллектора не используется, используется методWorker::collector
по умолчанию.
Новые классы
Класс Volatile
был добавлен благодаря новой семантике неизменяемости классов Threaded
, где, если у них есть свойства, являющиеся объектами Threaded
, они являются неизменяемыми. Класс Volatile
позволяет еще раз изменить код, который ранее зависел от изменчивости таких членов.
Например, следующий фрагмент кода работал бы на pthreads v2:
class Task extends Threaded { public function __construct() { $this->data = new Threaded(); $this->data = new StdClass(); // previously ok, but not in pthreads v3 } } new Task();
Но теперь в pthreads v3 переназначение $this->data
вызовет RuntimeException
так как это свойство Threaded
из класса Threaded
. Чтобы правильно переназначить свойство, класс Task
должен расширять Volatile
:
class Task extends Volatile { public function __construct() { $this->data = new Threaded(); $this->data = new StdClass(); } } new Task();
Массивы, назначаемые свойствам Threaded
объектов, теперь автоматически приводятся к Volatile
объектам, а не к Threaded
объектам, поэтому их поведение остается в основном неизменным.
Хотя это новое ограничение неизменности немного увеличивает сложность, оно было введено для значительного увеличения производительности, которое оно дает при доступе к Threaded
свойствам Threaded
объектов.
Новые Методы
Были добавлены следующие методы:
-
Worker::collect
— это было введено для освобождения задач, завершившихся в стеке работника. Необязательная функция сборщика может быть передана, однако сборщик по умолчанию (изWorker::collector
) должен быть достаточным в подавляющем большинстве случаев.Например, следующее:
$worker = new Worker(); var_dump(memory_get_usage()); // original memory usage for ($i = 0; $i < 500; ++$i) { $worker->stack(new class extends Threaded {}); } var_dump(memory_get_usage()); // memory usage after stacking 500 tasks $worker->start(); while ($worker->collect()); $worker->shutdown(); var_dump(memory_get_usage()); // memory usage after worker shutdown
Выходы:
int(372912) int(486304) int(374528)
Со строкой, которая вызывает
Worker::collect
, использование памяти почти возвращается к норме. Без этого использование памяти не изменилось бы между 500 сложенными задачами и выключением рабочего. Хотя память в конечном итоге освободилась бы после уничтожения объекта, лучше явно освободить эту память (особенно для долго выполняющихся процессов, которым может потребоваться выполнить много задач). Поэтому всегда собирайте мусор, оставленный рабочими (а также бассейнами). -
Worker::collector
— это было введено как реализация по умолчанию, используемая методомWorker::collect
. Мы можем переопределить этот метод для случаев, когда мы хотим отложить сбор потраченных предметов. Как упомянуто выше, коллектор по умолчанию будет достаточным в подавляющем большинстве случаев, поэтому переопределите этот метод, только если вы знаете, что делаете! -
Threaded::notifyOne
— это дополняетThreaded::notifyOne
, позволяя отправлять сигнал только в один из ожидающих синхронизированных контекстов.
Вывод
В pthreads v3 был внесен ряд изменений, сделавших расширение более производительным и надежным. Некоторые вещи стали проще, особенно в отношении совместно используемых ресурсов, которые могут обрабатываться только через механизмы синхронизации ( Threaded::wait
и Threaded::notify
). Другие вещи немного усложнились, особенно в отношении новых ограничений неизменности (в обмен на гораздо лучшую производительность). Но в целом, pthreads v3 получил хорошую очистку и выглядит все лучше.
Вы используете это? Пришлось обновляться с v2 до v3? Расскажите нам об этом — мы хотели бы написать о практическом примере обновления.