Пользователь
0,0
рейтинг
11 сентября 2013 в 03:26

Разработка → PHP IPC — Межпроцессное взаимодействие в PHP

PHP*

Целью данной заметки является ознакомление PHP-разработчиков с возможностями межпроцессного взаимодействия в данном языке. Заметка не предполагает во всех деталях рассказать о каждой из возможностей, деталях реализации или показать рабочие примеры кода.

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



Так же, будет задета тема многопоточности в PHP, причём, именно потоков (Thread), так как до недавнего времени, PHP позволял (удобно) реализовать какое либо распараллеливание лишь благодаря Fork (не будем касаться извращений типа curl_multi_*, popen\fsockopen, Apache MPM и т.п.). Тема будет рассмотрена лишь в контексте IPC, поиск деталей реализации того или иного подхода оставляю читателям.

Повествование будет вестись в разрезе ПО, работающего на одном компьютере, и IPC в рамках одной ЭВМ. Это связано с тем, что межпроцессное взаимодействие в распределённых системах — это весьма и весьма обширная тема. Потому, не будут рассматриваться всяческие брокеры сообщений, базы данных, Pub\Sub, и прочие “межкомпьютерные” подходы, к тому же, они освещены на других ресурсах в Сети.

Ввиду всего вышеперечисленного, от читателя требуется некоторая подготовка, так как терминология будет разъясняться лишь в ключевых моментах, однако, текст обильно снабжён ссылками на необходимые статьи документации PHP, википедии и т.п., а так же, понимание того, что многие вещи нарочно упрощены, ввиду формата данного материала.

What does IPC look like?


Итак, что же такое межпроцессное взаимодействие?

Межпроцессное взаимодействие — (англ. Inter-Process Communication, IPC) — набор способов обмена данными между множеством потоков в одном или более процессах. Процессы могут быть запущены на одном или более компьютерах, связанных между собой сетью. IPC-способы делятся на методы обмена сообщениями, синхронизации, разделяемой памяти и удаленных вызовов (RPC). Методы IPC зависят от пропускной способности и задержки взаимодействия между потоками и типа передаваемых данных.

Describe like IPC looks like!


План конспекта следующий:

0. PCNTL
1. Sockets
2. Shared Memory
3. Semaphore, Shared Memory and IPC
4. pthreads

0. PCNTL


Расширение реализует самый базовый функционал по работе с процессами, но нас интересует работа с сигналами UNIX, а конкретнее — функция pcntl_signal, которая позволяет установить обработчик сигналов. Этот подход самый малофункциональный из всех рассматриваемых, так как он не позволяет передавать данные. С помощью этого расширения можно, например, организовать старт\стоп воркеров, или считывание задач из буфера (файл, БД, память, etc.), или сигнализацию одной части системы другой о каком то событии.

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

1. Sockets

Сокеты — (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.

Следует различать клиентские и серверные сокеты. Клиентские сокеты грубо можно сравнить с оконечными аппаратами телефонной сети, а серверные — с коммутаторами. Клиентское приложение (например, браузер) использует только клиентские сокеты, а серверное (например, веб-сервер, которому браузер посылает запросы) — как клиентские, так и серверные сокеты.


Пожалуй, это самый очевидный и наиболее известный способ реализации IPC, однако, и самый трудозатратный. Первый вариант — это создать брокер (сокет-сервер), клиентами-потоками коннектиться к нему. Тут вас ждёт увлекательный мир отладки неблокирующего ввода\вывода (а как вы хотели — писать блокирующий код?), а так же реализация многих тривиальных вещей типа обёрток над функциями расширения. Второй вариант проще, его можно применять для более простых реализаций: create_socket_pair, которая создаёт пару связанных сокетов, пример доступен по ссылке.

Использование сокетов для реализации IPC требует довольно серьёзного подхода и раскуривания мануалов, но к плюсам можно отнести возможность в будущем разнести элементы системы на различные сервера, не прибегая к существенным правкам кода. Так же, плюсом данной технологии является её универсальность: скажем, написать клиент на PHP, соединяющийся с C-шным сервером не сложно.

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

2. Shared Memory

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

Вариантов использования множество: и общее пространство, и выделение блоков персонально под каждый поток\процесс, обработка данных так же упрощается ввиду чёткого определения размера блока. К недостаткам можно отнести некоторую сложность в удобной реализации такого взаимодействия: придётся пробрасывать адреса блоков в дочерние процессы (в виде параметров, при запуске pcntl_fork, с помощью файлов-маркеров etc.)

Данный подход является, пожалуй, наиболее распостранённым и предпочтительным, так как не требует больших трудозатрат в реализации, и является более универсальным.

3. Semaphore, Shared Memory and IPC

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

Семафоры могут пригодиться, когда потоки будут вынуждены работать с каким то общим ресурсом, скажем, вы написали файрволл, который будет при каждом запросе лезет в файлик с IP-адресами Роскомнадзора и делает с входящим запросом уличную магию. Файлик, понятное дело, обновляется каким то другим сервисным потоком, потому, недопустимо его чтение (или изменение), пока идёт процесс обновления, кем либо ещё. Теория работы семафоров проста, и примеров их реализации так же существует множество, потому, для тех, кто ещё не работал с этим типом блокировок, рекомендую ознакомиться, это поможет лучше понимать процессы построения взаимодействия между потоками.

Обмен сообщениями является более «высокоуровевым» и удобным решением, чем разделяемая память, но данная тема в контексте PHP плохо освещена. К тому же, мне известны случаи, когда данная технология проявляла некоторые странности, скажем так, в своей работе, потому, следует внимательно проверять и перепроверять результаты работы кода.

4. Pthreads

И вот мы достигли segfault'а вершины эволюции как IPC, так и многопоточности в PHP.

Классный дядька по имени Joe Watkins написал расширение pthread, которое добавляет в PHP поддержку самой настоящей многопоточности. Буквально на днях (8.09.2013) вышла первая stable-версия (0.0.45), однако, автор в своём посте на Reddit весьма подробно раскрыл тему beta\stable релизов, потому, не заостряйте на этом внимание. Настоятельно рекомендую изучить всего его комментарии в теме, там много полезной информации о pthread.

В чём же достоинства? Во всём. Pthreads предоставляет крайне простой и удобный API для реализации любых ваших многопоточных фантазий. Тут вам и synchronize как в джаве, и события, и IPC с пробросом объктов! С ними, правда, не всё так гладко (см. примеры на гитхабе), и автор пишет, что эта проблема — не его рук дело, однако, с ресурсами сокетов ему всё же удалось сотворить чудо, и теперь, результаты socket_accept из главного потока можно всунуть в дочерний — поразительно! Достаточно разобрать примеры, чтобы понять, насколько всё просто и элегантно сделано.

Описывать все возможности и прелести этого расширения не буду, всё есть на гитхабе автора и в документации на 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"

Does he look like a Pipe?


На этом, пожалуй, всё. Хотя язык и ограничен в своих возможностях IPC, тем не менее, он позволяет писать эффективные приложения с использованием различных подходов для реализации межпроцессного взаимодействия. Тем из вас, перед кем сейчас стоит задача реализации такого взаимодействия, рекомендую внимательно изучить все перечисленные в заметке способы, так как они не взаимозаменяемы, но эффективно дополняют друг друга.

P.S. Непосредственно к теме статьи это не относится, зато очень даже применимо к некоторым описанным здесь моментам, а именно, блокировании IO и несовершенстве событийной модели: рекомендую ознакомиться с расширениями Eio и Ev (автор обеих osmanov).
Александр @fox_hellraiser
карма
31,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (77)

  • –1
    Если вам хочеться cтранного то тогда, лучшим решением будет использовать библиотеку ØMQ
  • 0
    Есть еще Apache Thrift (http://thrift.apache.org/)
    • 0
      Ну и Google protobuf.
  • 0
    Тов. KAndy и lexdevel: целью заметки было описать решения и подходы, а не готовые продукты (за исключением pthreds), в контексте PHP для самостоятельного велосипедостроительства. Информация о 0mq, thrift и ещё куче подобны вещей гуглится сразу же, и не зря они небыли упомянуты, равно как и rmq, phpDaemon, nanoserv, ratchet, и ещё очень много других.
  • 0
    Еще есть named pipes.
    • 0
      Да, верно, совсем вылетело из головы. Расширение POSIX, функция posix_mkfifo
  • 0
    Сорри, что не [совсем] по теме, но меня мучает один вопрос, на который не смог сходу найти ответ, а сюда, прозреваю, заглянут хорошо разбирающиеся в вопросе люди. Суть вот в чем: у меня есть некий инит-скрипт, который запускается при старте системы и призван запустить несколько других скриптов в демон-режиме. Я использую примерно такой код:
    function bkg($executable, $args = []) {
        $childPid = pcntl_fork();
        if ($childPid == 0) {
            posix_setsid();
            fclose(STDOUT); // в манах говорится, что необходимо закрыть поток стд. вывода
            fclose(STDERR); // и ошибок
            pcntl_exec($executable, $args);
        } elseif($childPid != -1) {
            return true;
        }
        return false;
    }
    


    мне же в целях отладки надо перенаправить вывод запущенных процессов в какой-нибудь файл. Подскажите, плиз, как это можно сделать?
    • 0
      1) суслог
      2) перенаправляй все ошибки в лог
    • 0
      Вариант с перенаправлением потока вывода:

      function bkg(){
          $childPid = pcntl_fork();
          if ($childPid == 0) {
          } elseif($childPid != -1) {
              echo 'out to cli';
              fclose(STDOUT);
              $STDOUT = fopen("log." . getmypid() .  ".out", "wb");
              echo 'out to log.out';
         }
      }
      
      bkg();
      


      Или можно в дочернем процессе создавать лог-файл и вместо echo\print использовать файловые функции, это более предпочтительный вариант.
      • 0
        Хм, насколько я понял, данный код перенаправит вывод самого инит-скрипта, а необходимо перенаправить вывод именно вызываемых (при этом, к сожалению, я не имею возможности менять код вызываемых). Сработает этот прием, если переместить его обратно в секцию if ($childPid == 0)?
        • 0
          Ну вы же делаете fork в инит-скрипте N раз, который «запускает» N форков. Вот, для каждого из этих форков и происходит перенаправление вывода в свой файл. В итоге, у вас получится N файлов, каждый с выводом одного форка.

          Вот модифицированный код, который иллюстрирует написанное:

          <?php
          
          function bkg(){
              $childPid = pcntl_fork();
              if ($childPid == 0) {
              } elseif($childPid != -1) {
                  $pid = getmypid();
                  echo 'iam child, out to cli, my pid is ' . $pid . PHP_EOL;
                  fclose(STDOUT);
                  $STDOUT = fopen("log." . $pid .  ".out", "wb");
                  echo 'out to log.out, my pid is ' . $pid . PHP_EOL;
             } 
          }
          
          bkg();
          bkg();
          bkg();
          
          echo 'iam parent, out to cli, my pid is ' . getmypid() . PHP_EOL;
          
          ?>
          
          • +1
            Пардон, очепятался, так правильно:

            Скрытый текст
            <?php
            
            function bkg(){
                $childPid = pcntl_fork();
            
                if ($childPid == 0) {
                    $pid = getmypid();
                    echo 'iam child, out to cli, my pid is ' . $pid . PHP_EOL;
                    fclose(STDOUT);
                    $STDOUT = fopen("log." . $pid .  ".out", "wb");
                    echo 'iam child, out to log.' . $pid .  '.out, my pid is ' . $pid . PHP_EOL;
                } elseif($childPid != -1) {
               } 
            }
            
            bkg();
            bkg();
            bkg();
            
            echo 'iam parent, out to cli, my pid is ' . getmypid() . PHP_EOL;
            
            ?>
            
            • 0
              ага, я про то же ниже :)

              еще раз спасибо!
          • 0
            Вы во всем правы, единственное, путаете местами секции if.

            pcntl_fork() возвращает 0 именно в дочернем процессе, я попробовал переместить Ваш код в нужную секцию и все заработало как надо:
            function bkg($executable, $args){
                $childPid = pcntl_fork();
                if ($childPid == 0) {
                    posix_setsid();
                    fclose(STDOUT); // в манах говорится, что необходимо закрыть поток стд. вывода
                    fclose(STDERR); // и ошибок
                    $pid = getmypid();
                    $STDOUT = fopen("log." . $pid .  ".out", "wb");
                    echo 'out to log.out, my pid is ' . $pid . PHP_EOL;
                    pcntl_exec($executable, $args);
                } elseif($childPid != -1) {
                    $pid = getmypid();
                    echo 'i am still parent, out to cli, my pid is ' . $pid . PHP_EOL;
                }
            }
            
            bkg('/usr/bin/php', ['/domains/manuals.dev/script/tmp-child.php']);
            
            echo 'iam parent, out to cli, my pid is ' . getmypid() . PHP_EOL;
            


            Спасибо большое за наводку! (Я не знал, что существует еще и некая глобальная переменная $STDOUT)
            • 0
              Да, я уже внёс исправления, см. мой комментарий выше. vim-проказник подложил свинью :(
              • 0
                Не подскажете, откуда вам стало известно про $STDOUT? В документации такого не нашел, гугл тоже в данном случае не помощник.
                • +1
                  $STDOUT это просто перменная, можете назвать её иначе, как вам нравится
                  STDOUT это файловый дескриптор, который обычно открыт при запуске программы, запись в него приводит к отображению записанного на терминале, и также как и любой другой файловый дескриптор его можно закрыть функцией fclose, и если после этого открыть любой файл, то новый дескриптор будет иметь значение 1 и echo будет писать уже в него, потому что echo это по сути системный вызов write(1, ...)
                  • 0
                    О, так все проясняется. Название переменной в данном случае лишь вводит в заблуждение. Спасибо.
                • 0
                  Сществует множество стандартных потоков, которые имеют константные номера файловых дескрипторов (как ответили ниже), потому, никакой магии нет (документация на php.net). Обратите внимание на порядок закрытия\открытия стандартных потоков, имена переменных не важны, т.е, если вы хотите закрыть все три потока, а потом переоткрыть их, то открывайте в порядке возрастания их номеров (STDIN, STDOUT, STDERR), так как PHP автоматически назначает эти потоки, и явно указать, что вы сейчас открываете возможности нет. Это не касается зарезервированных потоков-обёрток типа «php://stdout».
    • 0
      Вообще-то я делаю, и это рекомендуется для всех, запуск приложения в двух режимах: демон или нет: разруливается опцией -d (daemon), запускается как php my_app -d в демоническом режиме

      Так вот в демоническом режиме должен быть отсоединен терминал и выводы std занулены
      Для отладки запускаем в отладочном режиме… Это нормальная практика, когда приложение имеет несколько режимов работы
      • 0
        Тут есть такой момент, что сам вызывающий скрипт не должен быть демоном, он стартует, запускает процессы и сразу завершается. А код вызываемых процессов, я менять не могу.
        • +1
          тут нет ничего сложного,
          я уже эти пользуюсь более пяти лет…
          как правило, когда что-то отлаживаешь, то работаешь в консоли и видишь в ней всю отладку.
          в этом случае, твой скрипт не делает форка, который его уводит в бэкграунд и работает функционал воркера.
          как только надо либо продакшен, то ставим в запускающем скрипте опцию, которая запускает функцию demonize()
          Эта функция состоит из форка, в котором завершается родительский процесс, отсоединяется терминал и гасятся стд выводы.

          Если твой скрипт запускает несколько демонических процессов, то так правило — это однотипные воркеры, т.е. ты можешь отлаживаться на одном воркере. А в продакшене запустить хоть 32 (так у меня было в онлайн-игре).

          Если твой скрипт запускает несколько демонов, но это разные по функциональности процессы, то что-то не то с архитектурой.

          Еще раз напоминаю, что можно просто настроить вывод ошибок в суслог и при необходимости его тайлись:
          tail -f /var/log/my_logs
          • 0
            Спс за инфо.

            Дело в том, что у меня немного другой кейс. Поясню. Мне нужно запускать не воркеры, а несколько сторонних скриптов в качестве демонов. Запускающий скрипт вызывается паппетом при старте системы (я юзаю вагрант). При этом, как я уже упоминал, у меня нет возможности настроить вывод в лог в самих этих скриптах, я могу править только сам вызывающий скрипт. Что самое досадное, когда я логинюсь через SSH в систему и запускаю стартующий скрипт из консоли, то и он и запускаемые скрипты работают без проблем, а вот когда запуск идет при старте системы, сторонние скрипты слетают (возможно, какие-то сервисы вроде редиса на тот момент еще не готовы, или еще что-то подобное). Исходя из этого мне и потребовалось узнать, что они там такое пишут в STDOUT и STDERR. Просто чтобы выяснить, откуда ноги растут.
          • 0
            Тащем та, fox_hellraiser уже подсказал годный для меня способ выше, но и Вам спасибо за участие!
        • +1
          function daemon()
          {
                  checkPid();
                  $pid =  pcntl_fork();
           
                  if ($pid < 0)
                          die('error call fork()'.PHP_EOL);
           
                  if ($pid)
                          exit();
           
                  echo 'start process pid=', posix_getpid(), PHP_EOL;
           
                  $sid = posix_setsid();
                  if ($sid < 0) exit;
           
                  global $pidFilename;
                  file_put_contents($pidFilename, posix_getpid());
           
                  fclose(STDOUT);
                  fclose(STDIN);
                  fclose(STDERR);
          }
      • 0
        Реализация собственной демонизации в общем-то не такая уж важная штука. Многие современные процесс-менеджеры наоборот предпочитают недемонизирующиеся сервисы. Ну и демонизацию всегда можно быстро добавить с помощью daemontools типа
        start-stop-daemon --start --background --chuid www-data:www-data --make-pidfile --pidfile $PIDFILE --exec $DAEMON -- $LOGFILE || return 1
        
        • 0
          это можно использовать если нужно запустить один процесс, а если у тебя пятьдесят worker-ов?
          • 0
            Не совсем понял. Если нужно запустить 50 воркеров то скорее всего стоит сделать один мастер-процесс, который нафоркает 50 детей. Но у этого мастера не обязательно реализовывать ещё и демонизацию (отвязывание от консоли).
            • 0
              Хотя некоторые менеджеры процессов (supervisord вроде бы) умеют запускать и N экземпляров процесса.
  • +3
    За обзор — спасибо, вполне доступно, правда все это давно всем известно, кто проработал на РНР более пары лет, а если и нет, то хорошо гуглится… Но, чтоб это все понять, прежде чем что-либо делать, советую прочитать про основы IPC у Стивенсона, где на примерах все хорошо рассказано и описано.

    А вот для новичков, лучше все разобрать на пальцах: конкретные и реальные примеры. Так что, непонятно, на кого рассчитана статья.
    • 0
      К сожалению, мой опыт говорит об обратном: мало кому известны все возможные способы, хотя бы просто списком, без деталей и нюансов. То же касается и гугления: чаще всего рекомендуют юзать сигналы и сокеты, варианты с БД и APC, про разделяемую память упоминают редко, а семафоры, очереди сообщений и pthreads и подавно.

      Потому, было решено собрать всё воедино, чтобы новички какраз и знали, что есть и с чего можно начать. К сожалению, с понятными примерами тоже не всё гладко: все эти технологии в контексте PHP так или иначе описаны на php.net и других интернетах с достаточным количеством приветмиров, а что то сложное городить — так новичкам будет сложно понять всю магию, а тем к то в теме оно и не надо. Так что, я рекомендую ознакомиться со всем, это не займёт много времени, а дальше углубляться в изучение выбранного направления.
      • 0
        [ offttop] Брать ведро попкорна?
        Ну начнем с конца, с самого интересного. Объясни мне хоть одну задачу, реализованную средствами РНР с использованием тредов и желательно с примерами?
        PS — а ты в курсе, что при использовании запросов в тредах — PHP сигфолтится?
        • 0
          Вы башорг парсить будете в один поток, или в несколько? А если не башорг, а уютненький фейсбучек? За рафинированными примерами можете сходить на гитхаб к автору pthreads, за нерафинированными — сесть и подумать, какие задачи в вебе могут быть решены не в один поток. Задачи, кстати, могут быть не только сайтописательскими, а и носить прикладной характер, скажем, морфологическая обработка неких текстов или иных больших массивов данных, генерация статистической информации и так далее.

          Другое дело, что PHP со своими форками\потоками, как известно каждому бородатому С-шнику, не для каждой задачи подходит, но ведь речь сейчас не об этом, верно? :)

          И о каких запросах идёт речь?
          • 0
            для задач парсинга есть multi_curl, который хорошо с этим справляется, В этих задача, основное время тратится на ожидание операций ввода/вывода, так что однопоточный режим режим именно самое то!

            Сколько уже писали на эту мету, что асинхронный мультеплексорный режим лучше чем синхронный многотредовый, так как создание каждого треда требует дополнительных ресурсов, где-то по 10М на тред (это только pthreads, не считая сколько потребует пхпешный враппер) + системные ресурсы на переключение контента и прочее…

            И в дополнение, в многопоточном режиме все равно в БД разными потоками не положишь.
            Так что для решение таких задач самое то: запустить сразу несколько процессов с мултикурлом, парсить и класть в БД,

            так что, про задачи вопрос не раскрыт.
            • 0
              Не уповайте на задежрки IO, есть куча задач, где длительный процесс их обработки нивелирует какие либо значимые задержки (загрузить данные в память и далее с ними работать), примеры таких задач я привёл. К тому же, многопоточная хитектура часто нужна про соображениям дизайна системы: вы таки будете писать сокет-сервер в один поток\процесс? Или применение принципов map-reduce в рамках одной машины, когда памяти не хватает, чтобы обработать весь массив данных, используя, скажем, сложную сортировку или выборку по какому то параметру?

              Да, и чего вы решили, что если многотредовый — то обязательно синхронный? Треды могут вообще не знать ничего о внешнем мире и соседях, делать лишь свою часть задачи, они могут быть инициализированы заранее в виде пула, как делает множество другого ПО. И про прожорливость тредов вы неправду пишете, поток pthreads жрёт ~4.6 Мб, но никак не 10.

              В дополнение, в многопоточном режиме данные можно сохранять в разные таблицы, и потом их мерджить (если это одна задача), мне знаком проект, где используются даже разные БД на свой пул форков. А уж возможности БД в виде отложенной записи, или синхронизация через кеши в памяти ещё сильнее ослабляют зависимость от медленного IO.

              Полагаю, что вопрос аргументации, что вопрос про задачи не раскрыт, не раскрыт.
              • 0
                >поток pthreads жрёт ~4.6 Мб, но никак не 10.
                зависит от настроек ядра, теоретически 4.6, но на практике более 10. Не спорил бы, если бы не решал многотрдовые задачи.
                • 0
                  более менее рабочее решение — это организация на каждый тред по своему евентлупу, но я не встречал нормальной реализации. Распределение коннекций по тнредам идет не нормально (не подчиняется нормальному закону распределения, т.е. не равномерно). Приходится городить костыли, типа считать кол-во коннекций и если больше среднего, то отпускать ассепт.
                  В итоге прходим к чему-то такому:
                  cnn[0]=1 0.39% [ 0.00]
                  cnn[1]=8 3.14% [ 0.03]
                  cnn[2]=7 2.75% [ 0.03]
                  cnn[3]=5 1.96% [ 0.02]
                  cnn[4]=7 2.75% [ 0.03]
                  cnn[5]=9 3.53% [ 0.04]
                  cnn[6]=5 1.96% [ 0.02]
                  cnn[7]=7 2.75% [ 0.03]
                  cnn[8]=7 2.75% [ 0.03]
                  cnn[9]=2 0.78% [ 0.01]
                  cnn[10]=7 2.75% [ 0.03]
                  cnn[11]=3 1.18% [ 0.01]
                  cnn[12]=7 2.75% [ 0.03]
                  cnn[13]=8 3.14% [ 0.03]
                  cnn[14]=9 3.53% [ 0.04]
                  cnn[15]=9 3.53% [ 0.04]
                  cnn[16]=9 3.53% [ 0.04]
                  cnn[17]=9 3.53% [ 0.04]
                  cnn[18]=7 2.75% [ 0.03]
                  cnn[19]=9 3.53% [ 0.04]
                  cnn[20]=9 3.53% [ 0.04]
                  cnn[21]=9 3.53% [ 0.04]
                  cnn[22]=9 3.53% [ 0.04]
                  cnn[23]=9 3.53% [ 0.04]
                  cnn[24]=9 3.53% [ 0.04]
                  cnn[25]=8 3.14% [ 0.03]
                  cnn[26]=8 3.14% [ 0.03]
                  cnn[27]=9 3.53% [ 0.04]
                  cnn[28]=5 1.96% [ 0.02]
                  cnn[29]=9 3.53% [ 0.04]
                  cnn[30]=2 0.78% [ 0.01]
                  cnn[31]=9 3.53% [ 0.04]
                  cnn[32]=7 2.75% [ 0.03]
                  cnn[33]=9 3.53% [ 0.04]
                  cnn[34]=9 3.53% [ 0.04]
                  — all=255 avg= 7.29

                  но это лучше, чем когда треть перегружена, стт=15 в вторая а треть cnn=0
                • 0
                  Помните хороший фильм, про бочку спирта в реке и про «это у тебя в уме, а у меня в бочке»? Вот так и тут: у вас «на практике» 10, а у меня на практике 4.6, с дефолтными настройками ядра на CentOS, которая ставилась по принципу «далее->далее->далее->ОК». ЧЯДНТ?
              • 0
                >Да, и чего вы решили, что если многотредовый — то обязательно синхронный?
                асинхронный в/в при многотредовости вообще превращается в ад

                нет ни одного нормального решения асинхронный I/O при многотредовой архитектуре.
                решение с передачей файлового дескриптора через внутреннюю очередь в тред, работает медленее чем асинхронный режим
                • 0
                  как один из выходов — можно использовать коротины. Но тут опять свои танцы с бубнами…

                  как один из недостатков — это загруженность только одного ядра. Ну а сами танцы начинаются при работе с БД…
                • 0
                  При работе с коротинами — есть возможность мускуль заставить работать асинхронно, но тут нужно отлавливать события асинхронности, и программирование превращается в кэллбэков ад.

                  короче — куда ни глянь, одни грабли…
                  • 0
                    короче — куда ни глянь, одни грабли…


                    «Мы выживали как могли» (С) :)
                • 0
                  Никакого ада нет, libevent и компания вам в помощь. В конце концов, можно изобрести велосипед (даже с круглыми колёсами) и без него, но не так элегантно и *троллейбус.jpg*. Опять же, зависит от конкретной задачи, я бы не стал так однозначно утверждать, что правильно, а что нет.

                  [оффтопик] nodejs же как то работает, и ничего: там вам и асинхронность, и коллбек-хэлл ада нет никакого.
                  • 0
                    node.js один evenloop — он ассептит дескриптор и передает его через очередь другим тредам.
                    uvlib была разобрана по косточкам :)
                  • 0
                    весь ад начинается, когда из треда надо делать более чем два связанных мускульных запроса. Если интересны подходы асинхронного мускуля, можешь почитать мою статью в сентябрьском выпуске «Системного Администратора»
                    • 0
                      статья выйдет в октябре.
                    • 0
                      А в чём проблема сделать два связанных запроса? Открываем транзакцию и делаем. Или, если данные запросов куда то нужно пробрасывать, скажем, в мастер-поток, так как тред по каким то причинам этого делать не может, то можно для этого сгородить специальную структуру (буфер).
          • 0
            Задачи, кстати, могут быть не только сайтописательскими, а и носить прикладной характер, скажем, морфологическая обработка неких текстов или иных больших массивов данных, генерация статистической информации и так далее.


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

            то же самое можно сказать и про морфологический разбор. Да и в онлайне он точно не нужен.

            ну про большие наборы данных ты вообще загнул. Загнется пхп на их обсчет. Реальный случай в моей практике, обсчет биллинга: foeach просто не выдержал обхода нескольких миллионов. Пришлось делать цикл через while. Ну, это было лет пять назад, а теперь просто юзаем MapReduce

            так что переходим к семафорам? расскажешь, как на практике надо использовать семафоры?
            • 0
              сам-то рассчитывал что либо?


              Конечно же нет, я тут просто новые баззворды осваиваю.

              практически все статистические расчеты есть — обработка одного большого массива данных.


              Про map-reduce вы, видимо, слышали, потому, это утверждение говорит лишь о том, о чём говорит: да, большое количество данных, но ведь и обработать их можно (относительно) без проблем. И на PHP такие вещи тоже можно писать, но не подумайте, я не агитирую за велосипеды и готов даже носить футболку с лого «Hadoop». Просто задачи бывают разных масштабов.

              то же самое можно сказать и про морфологический разбор. Да и в онлайне он точно не нужен.


              Правда? Почитайте про phpmorpfy, а лучше про его родителя pymorphy, где и кем они могут применяться. Почитайте про SEO (тут полёт фанзации по автогенерации слов\текстов неограничен), почитайте про корпусную лингвистику, про вольфрам-альфа, да хотя бы он-лайн переводчики. Веб — это давно не только сайтики с котятками, но и довольно серьёзные сервисы специфической направленности, и внутри у них может твориться много магии.

              Поо большие наборы данных я не загибал, а обрабатывал. И причин, почему ваш foreach упал, может быть множество, начиная от общего говнокода и заканчивая неправильным подходом к построению системы.

              так что переходим к семафорам? расскажешь, как на практике надо использовать семафоры?


              NO.
              • 0
                кончилось ведро попкорна?
  • 0
    Офтоп: удивился не обнаружив в тегах Тарантино. :)
    • 0
      Describe what Rasmus Lerdorf looks like!
  • 0
    Попробовал pthreads. Почему не работает данный код? Как будто поток не создается.

    <?php
    
    class MyThread extends Thread
    {
        public function run()
        {
            echo "Printing from a thread\n";
            sleep(2);
            echo "Printing from a thread\n";
        }
    }
    
    (new MyThread())->run();
    echo "Printing\n";
    
  • 0
    (new MyThread())->run();
    

    замените на

    $thread = new MyThread();
    $thread->start();
    
    • 0
      Точно, спасибо, такая глупая ошибка :)
    • 0
      Ошибка у меня эта была, исправил, однако поведение не изменилось.
      • 0
        Вы написали именно так?

        $thread = new MyThread();
        $thread->start();
        
        • 0
          Нет, я написал это в одной строке.
          Попробовал написать именно так — заработало. Объясните пожалуйста, в чем тут магия?
          • 0
            Магия в неявном создании переменной, хранящей объект класса, такое нововведение некорректно обрабатывается, предполагаю, потому, что pthreads использует некоторые… неоднозначные вещи в коде, и потому, автор мог не учесть всех, нюансов zend'а. Ответ следует искать в работе менеджера памяти и сборщика мусора, ведь, в таком случае, после завершения вызова, объект уже как бы «бесхозный». К тому же, насколько мне известно, автор активно работает с версией 5.3, потому, мог не уследить за всеми тонкостями новых версий
  • 0
    Обзор не полон. Можно было бы привести хотя бы основы работы с сигналами(в частности с наиболее частыми сигналами вида SIGTERM, SIGCHILD e.t.c), системными сообщениями. Вы забыли такие важные вещи почему-то в контексте языка, как Gearman(кстати это наилучший способ подружить php и C), демоны очередей(~MQ), и события(их C-шные реализации кстати достаточно простые) Libevent, Libevent2, помимо упомянутых официально-неофициальное php-расширение Event сделанное по мотивам LibEV только без неймспейсов и гораздо более функциональное. И раз уж затронули тему сокетов, то можно было бы хоть написать вкратце разницу между блокирующими и неблокирующими.
    Но как почва для размышлений — статья это отличное начало!
    • +2
      Я уже отвечал на подобные вопросы, и в заметке об этом сказано, но всё же повторюсь: целью было рассмотреть IPC лишь в контексте PHP, и не готовый софт, а сами принципы\подходы (и их реализации в языке), а то, о чём вы пишете, хоть и работает с ним, но область применения и возможности далеко не ограничиваются одним PHP. К тому же, эти решения гуглятся довольно быстро, а уж в документации и примерах недостатка нет (Кстати, о сигналах тоже вагон информации, даже на php.net).

      Собственно, постигать дзен типа различий в IO и тонкостях каждого подхода, читателю следует самому, так как раскрытие таких деталей в статье потянет за собой ещё и описание решений, которые позволяют обойти те или иные ограничения (читай libev* и co), а тут уже полёт фантазии не ограничен.

      О тех же 0mq и gearman`ах многие хотя бы слышали, а вот о таких, казалось бы, базовых вещах — нет. Потому, вы верно заметили — это почва для размышлений, причём, рассчитанная на более-менее опытных пользователей, которая бы как то систематизировала их представления о вариантах реализации IPC в PHP.
      • 0
        согл с автором, это тема отдельной большой статьи,
        и эту тему уже не раз раскрывали на Хабре
  • 0
    Хороший обзор, хотелось бы примеров каждого из пунктов, буду ждать ещё статью :)

    Почему это popen извращение? Сам popen это конечно же только односторонний IPC, но есть более продвинутый вариант proc_open. Благодаря которому получается unix-way взаимодействие между процессами через пайп.
    Обе эти функции находятся в «экстеншене» core PHP, а это значит, что не нужно дополнительно собирать модули так, как это требуется для тех же pcntl и sockets, не говоря уже про кастомное расширение pthreads.
    • 0
      sockets тоже не нужно
    • 0
      Промахнулся веткой, ответил вам ниже.
  • +1
    Примеры, к сожалению, противоречат идеологии заметки, но, я подумаю :)

    По поводу proc_open: это довольно неудобное решение (в сравнении с остальными), к тому же, одним proc_open вы не обойдётесь, придётся задействовать ещё и расширение Stream, а так же файловые функции для чтения\записи (или их аналоги из stream_*), перенаправления вывода, что при разработке чего то дальше приветмира оборачивается хаосом в коде, под всё это придётся писать обёртки и собирать в итоге под каким то удобоваримом интерфейсом. Ещё у этого решения есть проблемы с блокирующимся pipe-ами, с кодировкой что то было, на Windows (хотя это малозначимая проблема в 99% случаев) вообще можно словить цирк и феерию с ограничениями размера буферов. С popen припоминаю проблемы при chroot, при чтении бинарных данных.

    Короче говоря, это работающий метод, но я бы крайне не рекомендовал его использование, в особенности, когда есть более приемлимые решения, лучше уж в файлики писать :)
    • +1
      На самом деле интересует реальный опыт используемого вами метода IPC.

      По моему опыту мне хватило proc_open для задачи взаимодействия одного мастер процесса и десяти дочерних, все функции для этого есть в стандратном экстеншене:

      $ php --re standard | grep -E 'fwrite|fgets|stream_select|proc_open'
          Function [ <internal:standard> function proc_open ] {
          Function [ <internal:standard> function fgets ] {
          Function [ <internal:standard> function fwrite ] {
          Function [ <internal:standard> function stream_select ] {
      


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

      С файлами проблема в том, что пишущий процесс должен закрыть файл, чтобы читащий мог нормально читать данные, чтобы не нужно было отслеживать последнюю прочитанную позицию, кажется как-то так.
      • +1
        Я использовал все приведённые в статье способы в виде готовых, рабочих решений, кроме pthreads — с ним я только писал хеллоуВорлды и пускал скупую слезу восхищения.

        сигналы — в простейших системах типа «стартуем N процессов и откуда то ими рулим», или для синхронизации;
        сокеты — для сервера\клиентов очереди сообщений, велосипедостроения типа селениума, коммуникации с питон-скриптами, да много где ещё;
        шаред мемори и семафоры — в проектах для работы с внешними сервисами по API, когда требовалась синхронизация по определённым критериями, так как API, так сказать, «непотокобезопастный», и не требовалось активного обмена данными между потоками и их раскидыванием по разным машинам, а так же по работе с ffmpeg;
        «извращения» — в молодые зелёные годы в поисках решений по сабжу заметки :) (вероятно, информация об описанных мною проблемах могла стать неактуальной)

        Где то между всем этим проскакивал и libevent.

        Это всё, что касается велосипедостроения, но, иногда в силу разных причин, нет возможности использовать какие то готовые решения, потому, пришлось осваивать и это.

        Про файлики я не всерьёз, но и там можно сделать ход конём с помощью функций stream_* и ловить кайф синхронизации данных :)
    • 0
      расширение Stream

      Потоки являются неотъемлемой частью PHP, начиная с версии 4.3.0. Для их включения не требуется каких-либо действий.


      Кстати, «PCNTL» и «Semaphore, Shared Memory and IPC» под Windows не работают :( Проблема с бинарным режимом (по умолчанию винда работает с файлами в текстовом режиме) решается указанием флага «b» в "['pipe', 'rb']" (там еще несколько режимов есть, но они могут не работать в *nix)

      Настоящие же проблемы в том что:
      1) stream_select возвращал все потоки вместо измененного (на win 8 x64 и PHP 5.4.16 сейчас вроде нормально работает)
      2) stream_set_blocking НЕ работает с proc_open, т.е. неблокирующего режима просто нет :) (а нет его потому, что неименованные пайпы, через которые это все работает, в винде этого не умеют)

      Поэтому для чего то серьезного оно действительно не подходит.
      • 0
        Я не говорил «установить», я говорил «задействовать» расширение Stream, подразумевая лишь его использование, а не установку или что то ещё.

        Да, под виндой это не работает, так как основано на чисто никсовых системах (System V IPC, etc), которые, по понятным причинам, на винду портировать несколько… затруднительно. На Windows не акцентировал внимание в заметке, так как уже давно под ней ничего не пишу, да и абсолютное большинство PHP-программистов не используют WIndows в produсtion, потому, я полагал, что всё перечисленное в заметке будет по-умолчанию применяться в *.nix и под него же адаптироваться.

        Однако, оба пункта отлично работают на *nix, и серьёзные вещи с использованием этих подходов можно писать. В догонку о proc_open, цитата из доки по stream_select: «Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.»
  • 0
    А какой из вариантов работает без установки дополнительных расширений или пересобирания PHP?
    • 0
      popen/proc_open и скорее всего socket-ы.
    • 0
      Можно ещё обычные файлы использовать, и, если не нужен неблокирующий IO — использовать flock и co. для самописного «мьютекса».
      • 0
        мой мозг сломался после фразы о неблокирующем IO на flock для самописных мьютексов. Просто пока нету расширений для работы с частными случаями winAPI на php потому что это никому не нужно в рамках IPC — а с системными блокировками и там и там на самом деле все в порядке.
        • 0
          неблокирующем IO на flock


          Я писал, что если неблокирующий IO НЕ нужен, то можно задействовать flock, да и не рассматриваю Windows, как платформу для разработки.
          • 0
            тем более flock на ней и не работает как заявлено.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.