Многопоточность на PHP

Хочу рассказать вам про многопоточные приложения, взаимодействие процессов (IPC) и применение всего этого на PHP. В качестве примера мы возьмем счетчик с эксклюзивным доступом, доступный для всех процессов.

Что такое многопоточность? Это способ разделения программы на отдельные сущности (процессы и треды(threads)) для обеспечивания параллельности выполнения. Зачем это нужно? Для ускорения работы. Ведь открывая какой-нибудь сайт вы не хотите ждать пока сервер последовательно откроет его всем, кто запросил его до вас. Вы хотите сразу, поэтому сервер выделяет отдельный процесс или поток для обработки вашего запроса.

Сначала немного про многопточность в PHP. PHP не многопоточный язык, PHP не умеет работать с тредами. Однако мы можем использовать системные вызовы операционной системы для создания процессов. Это как если бы мы просто запускали PHP-CLI несколько раз. Основная команда pcntl_fork разделяет выполнение программы на 2 части: родительскую и дочернюю, при этом эта команда вернет идентификатор процесса. Если вернулся -1, значит произошла ошибка, если 0, значит мы в дочернем потоке, если число — значит в родительском.

$pid = pcntl_fork(); if ($pid == -1) { die(‘Произошла ошибка’); } else if ($pid) { // Мы в родительском потоке pcntl_wait($status); //Ждем выполнения потомка } else { // Мы в потоке потомке // Делаем что-нибудь }

В этом примере мы разделили программу на 2 части. В родительском процессе мы ожидаем завершения дочернего, чтобы тот доложил об успешном завершении. В противном случае он останется висеть в системе, как процесс-зомби. А если мы хотим не 2 потока, а больше? Тогда вызываем pcntl_fork несколько раз.

Приведу пример, как можно использовать pcntl_fork для «демонизации» процесса. Демон (daemon) — процесс выполняющийся в фоновом режиме. Это бывает очень удобно для программ, которые выполняются долгое время и не требуют прямого общения с пользователем. В Windows службы — аналог демонов в *nix системах. Итак, у вас есть программа на PHP, выполняющаяся в командной строке и вы хотите, чтобы она работала в фоне. Конечно, вы всегда можете сделать так:

nohup php script.php &

Это запустит вашу программу в фоновом режиме, а весь вывод будет перенаправлять в nohup.out. Более того, программа не завершится с выходом пользователя из системы. Но для демонов более правильный вариант создать процесс-потомок, закрыть родительский процесс (да-да, почти как зомби), только мы еще назначим наш дочерний поток «лидером сессии», то есть он сам сможет порождать дочерние потоки.

//Demonization $child_pid = pcntl_fork(); // Если мы получили дочерний id, // то мы в родительском потоке if ($child_pid) { exit; } //Здесь мы уже в потомке, а родитель был закрыл posix_setsid();

Таким образом мы получим независимо выполняющуюся программу. Завершить ее можно командой kill pid, где pid — идентификатор процесса. Тот самый, который возвращается в родительский поток и представляет собой целое неотрицательное число, уникальное в системе.

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

Сигналы — это системные вызовы, влияющие на поведение процессов. Сигнал может завершить, приостановить, или послать особый сигнал, определенный пользователем. Помимо сигналов есть разделяемая память (shared memory) и семафоры (semaphores). Более подробно о них, и вообще об устройстве posix совместимых операционных систем советую вам поискать в интернете. Это было краткое введение в тему, для подготовки к следующей статье про счетчик в shared memory.

Leave a Reply

Your email address will not be published. Required fields are marked *