- PVSM.RU - https://www.pvsm.ru -

Целью данной заметки является ознакомление PHP-разработчиков с возможностями межпроцессного взаимодействия в данном языке. Заметка не предполагает во всех деталях рассказать о каждой из возможностей, деталях реализации или показать рабочие примеры кода.
Поскольку задача распараллеливания рано или поздно появляется у любого программиста, то данная заметка была задумана отправной точкой, с которой можно начать своё путешествие в мир увлекательного геморроя процесса построения таких систем.
Так же, будет задета тема многопоточности в PHP, причём, именно потоков (Thread [1]), так как до недавнего времени, PHP позволял (удобно) реализовать какое либо распараллеливание лишь благодаря Fork [2] (не будем касаться извращений типа curl_multi_*, popenfsockopen, Apache MPM и т.п.). Тема будет рассмотрена лишь в контексте IPC, поиск деталей реализации того или иного подхода оставляю читателям.
Повествование будет вестись в разрезе ПО, работающего на одном компьютере, и IPC в рамках одной ЭВМ. Это связано с тем, что межпроцессное взаимодействие в распределённых системах — это весьма и весьма обширная тема. Потому, не будут рассматриваться всяческие брокеры сообщений, базы данных, PubSub, и прочие “межкомпьютерные” подходы, к тому же, они освещены на других ресурсах в Сети.
Ввиду всего вышеперечисленного, от читателя требуется некоторая подготовка, так как терминология будет разъясняться лишь в ключевых моментах, однако, текст обильно снабжён ссылками на необходимые статьи документации PHP, википедии и т.п., а таК же, понимание того, что многие вещи нарочно упрощены, ввиду формата данного материала.
Итак, что же такое межпроцессное взаимодействие?
Межпроцессное взаимодействие [3] — (англ. Inter-Process Communication, IPC) — набор способов обмена данными между множеством потоков в одном или более процессах. Процессы могут быть запущены на одном или более компьютерах, связанных между собой сетью. IPC-способы делятся на методы обмена сообщениями, синхронизации, разделяемой памяти и удаленных вызовов (RPC). Методы IPC зависят от пропускной способности и задержки взаимодействия между потоками и типа передаваемых данных.
План конспекта следующий:
0. PCNTL [4]
1. Sockets [5]
2. Shared Memory [6]
3. Semaphore, Shared Memory and IPC [7]
4. pthreads [8]
Расширение реализует самый базовый функционал по работе с процессами, но нас интересует работа с сигналами UNIX [9], а конкретнее — функция pcntl_signal [10], которая позволяет установить обработчик сигналов. Этот подход самый малофункциональный из всех рассматриваемых, так как он не позволяет передавать данные. С помощью этого расширения можно, например, организовать стартстоп воркеров, или считывание задач из буфера (файл, БД, память, etc.), или сигнализацию одной части системы другой о каком то событии.
Наиболее просто реализуема, есть множество примеров, и возможностей в применении, часто может более чем хватить для каких то не сильно сложных задач.
Сокеты [11] — (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.
Следует различать клиентские и серверные сокеты. Клиентские сокеты грубо можно сравнить с оконечными аппаратами телефонной сети, а серверные — с коммутаторами. Клиентское приложение (например, браузер) использует только клиентские сокеты, а серверное (например, веб-сервер, которому браузер посылает запросы) — как клиентские, так и серверные сокеты.
Пожалуй, это самый очевидный и наиболее известный способ реализации IPC, однако, и самый трудозатратный. Первый вариант — это создать брокер (сокет-сервер), клиентами-потоками коннектиться к нему. Тут вас ждёт увлекательный мир отладки неблокирующего вводавывода (а как вы хотели — писать блокирующий код?), а так же реализация многих тривиальных вещей типа обёрток над функциями расширения. Второй вариант проще, его можно применять для более простых реализаций: create_socket_pair [12], которая создаёт пару связанных сокетов, пример доступен по ссылке.
Использование сокетов для реализации IPC требует довольно серьёзного подхода и раскуривания мануалов, но к плюсам можно отнести возможность в будущем разнести элементы системы на различные сервера, не прибегая к существенным правкам кода. Так же, плюсом данной технологии является её универсальность: скажем, написать клиент на PHP, соединяющийся с C-шным сервером не сложно.
Так же, следует отменить и минусы: упомянутый выше неблокирующий IO. Так как данные будут поступать порциями, по следует хорошо подумать о механизме обеспечения их целостности, буферизации и обработке, которая бы не свела насмарку все преимущества неблокирующего вводавывода.
Данное расширение позволяет полноценно работать с виртуальной памятью [13]. Плюсы подхода в том, что он является наиболее быстрым (если быстродействие поставлено во главу угла приложения) и наименее ресурсозатратным. К тому же, его реализация не сопряжена с таким количеством подводных камней, как в предыдущем решении, да и сама технология не является трудноусваиваемой.
Вариантов использования множество: и общее пространство, и выделение блоков персонально под каждый потокпроцесс, обработка данных так же упрощается ввиду чёткого определения размера блока. К недостаткам можно отнести некоторую сложность в удобной реализации такого взаимодействия: придётся пробрасывать адреса блоков в дочерние процессы (в виде параметров, при запуске pcntl_fork [14], с помощью файлов-маркеров etc.)
Данный подход является, пожалуй, наиболее распостранённым и предпочтительным, так как не требует больших трудозатрат в реализации, и является более универсальным.
Это расширение включает в себя возможности предыдущего, однако, добавляет такие базовые возможности синхронизации ресурсов, как семафоры, другой способ взаимодействия потоков, известный как обмен сообщениями. [15]
Семафоры могут пригодиться, когда потоки будут вынуждены работать с каким то общим ресурсом, скажем, вы написали файрволл, который будет при каждом запросе лезет в файлик с IP-адресами Роскомнадзора и делает с входящим запросом уличную магию. Файлик, понятное дело, обновляется каким то другим сервисным потоком, потому, недопустимо его чтение (или изменение), пока идёт процесс обновления, кем либо ещё. Теория работы семафоров проста, и примеров их реализации так же существует множество, потому, для тех, кто ещё не работал с этим типом блокировок, рекомендую ознакомиться, это поможет лучше понимать процессы построения взаимодействия между потоками.
Обмен сообщениями является более «высокоуровевым» и удобным решением, чем разделяемая память, но данная тема в контексте PHP плохо освещена. К тому же, мне известны случаи, когда данная технология проявляла некоторые странности, скажем так, в своей работе, потому, следует внимательно проверять и перепроверять результаты работы кода.
И вот мы достигли segfault'а вершины эволюции как IPC, так и многопоточности в PHP.
Классный дядька по имени Joe Watkins [16] написал расширение pthread [17], которое добавляет в PHP поддержку самой настоящей многопоточности. Буквально на днях (8.09.2013) вышла первая stable-версия (0.0.45), однако, автор в своём посте [18] на Reddit весьма подробно раскрыл тему betastable релизов, потому, не заостряйте на этом внимание. Настоятельно рекомендую изучить всего его комментарии в теме, там много полезной информации о pthread.
В чём же достоинства? Во всём. Pthreads предоставляет крайне простой и удобный API для реализации любых ваших многопоточных фантазий. Тут вам и synchronize как в джаве, и события, и IPC с пробросом объктов! С ними, правда, не всё так гладко (см. примеры [19] на гитхабе), и автор пишет, что эта проблема — не его рук дело, однако, с ресурсами сокетов ему всё же удалось сотрворить чудо, и теперь, результаты socket_accept [20] из главного потока можно всунуть в дочерний — поразительно! Достаточно разобрать примеры, чтобы понять, насколько всё просто и элегантно сделано.
Описывать все возможности и прелести этого расширения не буду, всё есть на гитхабе автора и в документации [8] на php.net
Судя по всему, автор довольно интенсивно работает над своим проектом, потому, в будущем у него может появиться ещё много интересных возможностей, stay tuned.
Для запуска расширения необходимо собрать PHP в Thread-safe режиме, вот небольшой скрипт, который сделает всё за вас:
mkdir /opt/php-ts &&
cd /opt/php-ts &&
wget http://www.php.net/get/php-5.5.3.tar.bz2/from/ua1.php.net/mirror -O php-5.5.3.tar.bz2 &&
tar -xf php-5.5.3.tar.bz2 &&
cd php-5.5.3/ext &&
git clone https://github.com/krakjoe/pthreads.git &&
cd ../ &&
./buildconf --force &&
./configure --disable-all --enable-pthreads --enable-maintainer-zts &&
make &&
TEST_PHP_EXECUTABLE=sapi/cli/php sapi/cli/php run-tests.php ext/pthreads &&
alias php-ts="/opt/php-ts/php-5.5.3/sapi/cli/php"
На этом, пожалуй, всё. Хотя язык и ограничен в своих возможностях IPC, тем не менее, он позволяет писать эффективные приложения с использованием различных подходов для реализации межпроцессного взаимодействия. Тем из вас, перед кем сейчас стоит задача реализации такого взаимодействия, рекомендую внимательно изучить все перечисленные в заметке способы, так как они не взаимозаменяемы, но эффективно дополняют друг друга.
P.S. Непосредственно к теме статьи это не относится, зато очень даже применимо к некоторым описанным здесь моментам, а именно, блокировании IO и несовершенстве событийной модели: рекомендую ознакомиться с расширениями Eio [21] и Ev [22] (автор обеих osmanov [23]).
Источник [24]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/43023
Ссылки в тексте:
[1] Thread: http://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%82%D0%BE%D0%BA_%D0%B2%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F
[2] Fork: http://ru.wikipedia.org/wiki/%D0%A4%D0%BE%D1%80%D0%BA
[3] Межпроцессное взаимодействие: http://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%B6%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%D0%BD%D0%BE%D0%B5_%D0%B2%D0%B7%D0%B0%D0%B8%D0%BC%D0%BE%D0%B4%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%B8%D0%B5
[4] PCNTL: http://php.net/manual/en/book.pcntl.php
[5] Sockets: http://php.net/manual/ru/book.sockets.php
[6] Shared Memory: http://php.net/manual/en/book.shmop.php
[7] Semaphore, Shared Memory and IPC: http://php.net/manual/en/book.sem.php
[8] pthreads: http://php.net/manual/en/book.pthreads.php
[9] сигналами UNIX: http://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%B3%D0%BD%D0%B0%D0%BB%D1%8B_(UNIX)
[10] pcntl_signal: http://php.net/manual/en/function.pcntl-signal.php
[11] Сокеты: http://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D0%BA%D0%B5%D1%82_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%BD%D1%8B%D0%B9_%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81)
[12] create_socket_pair: http://www.php.net/manual/ru/function.socket-create-pair.php
[13] виртуальной памятью: http://ru.wikipedia.org/wiki/%D0%92%D0%B8%D1%80%D1%82%D1%83%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D1%8C
[14] pcntl_fork: http://php.net/manual/ru/function.pcntl-fork.php
[15] обмен сообщениями.: http://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D0%BC%D0%B5%D0%BD_%D1%81%D0%BE%D0%BE%D0%B1%D1%89%D0%B5%D0%BD%D0%B8%D1%8F%D0%BC%D0%B8
[16] Joe Watkins: https://github.com/krakjoe
[17] pthread: https://github.com/krakjoe/pthreads
[18] посте: http://www.reddit.com/r/PHP/comments/1jo517/multithreading_in_php_with_pthreads/
[19] примеры: https://github.com/krakjoe/pthreads/tree/master/examples
[20] socket_accept: http://php.net/manual/ru/function.socket-accept.php
[21] Eio: http://www.php.net/manual/en/book.eio.php
[22] Ev: http://www.php.net/manual/en/book.ev.php
[23] osmanov: http://habrahabr.ru/users/osmanov/
[24] Источник: http://habrahabr.ru/post/193270/
Нажмите здесь для печати.