Многопоточные вычисления в PHP: pthreads

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



    Я рассматриваю такую задачу. Есть пул заданий, которые надо побыстрее выполнить. В PHP есть и другие инструменты для решения этой задачи, тут они не упоминаются, статья именно про pthreads.


    Стоит отметить, что автор расширения, Joe Watkins, в своих статьях предупреждает, что многопоточность — это всегда не просто и надо быть к этому готовым.


    Кто не испугался, идем далее.


    Что такое pthreads


    Pthreads — это объектно-ориентированное API, которое дает удобный способ для организации многопоточных вычислений в PHP. API включает в себя все инструменты, необходимые для создания многопоточных приложений. PHP-приложения могут создавать, читать, писать, исполнять и синхронизировать потоки с помощью объектов классов Threads, Workers и Threaded.


    Что внутри pthreads


    Иерархия основных классов, которые мы только что упомянули, представлена на диаграмме.


    Threaded — основа pthreads, дает возможность параллельного запуска кода. Предоставляет методы для синхронизации и другие полезные методы.


    Thread. Можно создать поток, отнаследовавшись от Thread и реализовав метод run(). Метод run() начинает выполняться, причем в отдельном потоке, в момент, когда вызывается метод start(). Это можно инициировать только из контекста, который создает поток. Объединить потоки можно тоже только в этом-же контексте.


    Worker. Персистентное состояние, которое в большинстве случаев используется разными потоками. Доступно, пока объект находится в области видимости или до принудительного вызова shutdown().


    Помимо этих классов есть еще класс Pool. Pool — пул (контейнер) Worker-ов можно использовать для распределения Threaded объектов по Worker-ам. Pool — наиболее простой и эффективный способ организовать несколько потоков.


    Не будем сильно грустить над теорией, а сразу попробуем все это на примере.


    Пример


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


    Так давайте приступим. Для этого создадим провайдер данных MyDataProvider (Threaded), он будет один и общий для всех потоков.


    /**
     * Провайдер данных для потоков
     */
    class MyDataProvider extends Threaded
    {
        /**
         * @var int Сколько элементов в нашей воображаемой БД
         */
        private $total = 2000000;
    
        /**
         * @var int Сколько элементов было обработано
         */
        private $processed = 0;
    
        /**
         * Переходим к следующему элементу и возвращаем его
         * 
         * @return mixed
         */
        public function getNext()
        {
            if ($this->processed === $this->total) {
                return null;
            }
    
            $this->processed++;
    
            return $this->processed;
        }
    }

    Для каждого потока у нас будет MyWorker (Worker), где будет храниться ссылка на провайдер.


    /**
     * MyWorker тут используется, чтобы расшарить провайдер между экземплярами MyWork.
     */
    class MyWorker extends Worker
    {
        /**
         * @var MyDataProvider
         */
        private $provider;
    
        /**
         * @param MyDataProvider $provider
         */
        public function __construct(MyDataProvider $provider)
        {
            $this->provider = $provider;
        }
    
        /**
         * Вызывается при отправке в Pool.
         */
        public function run()
        {
            // В этом примере нам тут делать ничего не надо
        }
    
        /**
         * Возвращает провайдера
         * 
         * @return MyDataProvider
         */
        public function getProvider()
        {
            return $this->provider;
        }
    }

    Сама обработка каждой задачи пула, (пусть это будет некая ресурсоемкая операция), наше узкое горлышко, ради которого мы и затеяли многопоточность, будет в MyWork (Threaded).


    /**
     * MyWork это задача, которая может выполняться параллельно
     */
    class MyWork extends Threaded
    {
    
        public function run()
        {
            do {
                $value = null;
    
                $provider = $this->worker->getProvider();
    
                // Синхронизируем получение данных
                $provider->synchronized(function($provider) use (&$value) {
                   $value = $provider->getNext();
                }, $provider);
    
                if ($value === null) {
                    continue;
                }
    
                // Некая ресурсоемкая операция
                $count = 100;
                for ($j = 1; $j <= $count; $j++) {
                    sqrt($j+$value) + sin($value/$j) + cos($value);
                }
            }
            while ($value !== null);
        }
    
    }

    Обратите внимание, что данные из провайдера забираем в synchronized(). Иначе есть вероятность часть данных обработать более 1 раза, или пропустить часть данных.
    Теперь заставим все это работать с помощью Pool.


    require_once 'MyWorker.php';
    require_once 'MyWork.php';
    require_once 'MyDataProvider.php';
    
    $threads = 8;
    
    // Создадим провайдер. Этот сервис может например читать некие данные
    // из файла или из БД
    $provider = new MyDataProvider();
    
    // Создадим пул воркеров
    $pool = new Pool($threads, 'MyWorker', [$provider]);
    
    $start = microtime(true);
    
    // В нашем случае потоки сбалансированы. 
    // Поэтому тут хорошо создать столько потоков, сколько процессов в нашем пуле.
    $workers = $threads;
    for ($i = 0; $i < $workers; $i++) {
        $pool->submit(new MyWork());
    }
    
    $pool->shutdown();
    
    printf("Done for %.2f seconds" . PHP_EOL, microtime(true) - $start);

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


    Вот и все! Ну почти все. На самом деле есть то, что может огорчить пытливого читателя. Все это не работает на стандартном PHP, скомпилированным с опциями по умолчанию. Чтобы насладиться многопоточностью, надо, чтобы в вашем PHP был включен ZTS (Zend Thread Safety).


    Настройка PHP


    В документации сказано, что PHP должен быть скомпилирован с опцией --enable-maintainer-zts. Я не пробовал сам компилировать, вместо этого нашел пакет для Debian, который и установил себе.


    sudo add-apt-repository ppa:ondrej/php-zts
    sudo apt update
    sudo apt-get install php7.0-zts php7.0-zts-dev

    Таким образом у меня остался прежний PHP, который запускается из консоли обычным образом, с помощью команды php. Соответственно, веб сервер использует его-же. И появился еще один PHP, который можно запускать из консоли через php7.0-zts.


    После этого можно ставить расширение pthreads.


    git clone https://github.com/krakjoe/pthreads.git
    ./configure
    make -j8
    sudo make install
    echo "extension=pthreads.so" > /etc/pthreads.ini
    sudo cp pthreads.ini /etc/php/7.0-zts/cli/conf.d/pthreads.ini

    Вот теперь все. Ну… почти все. Представьте, что вы написали мультипоточный код, а PHP на машине у коллеги не настроен соответствующим образом? Конфуз, не правда ли? Но выход есть.


    pthreads-polyfill


    Тут снова спасибо Joe Watkins за пакет pthreads-polyfill. Суть решения такова: в этом пакете содержатся те-же классы, что и в расширении pthreads, они позволяют выполниться вашему коду, даже если не установлено расширение pthreads. Просто код будет выполнен в один поток.
    Чтобы это заработало, вы просто подключаете через composer этот пакет и больше ни о чем не думаете. Там происходит проверка, установлено ли расширение. Если расширение установлено, то на этом работа polyfill заканчивается. Иначе подключаются классы-”заглушки”, чтобы код работал хотя бы в 1 поток.


    Проверим


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


    Информация о процессоре, на котором запускал тесты


    $ lscpu
    CPU(s):                8
    Потоков на ядро:       2
    Ядер на сокет:         4
    Model name:            Intel(R) Core(TM) i7-4700HQ CPU @ 2.40GHz
    

    Посмотрим диаграмму загрузки ядер процессора. Тут все соответствует ожиданиям.


    $threads = 1


    $threads = 1


    $threads = 2


    $threads = 2


    $threads = 4


    $threads = 4


    $threads = 8


    $threads = 8


    А теперь самое главное, ради чего все это. Сравним время выполнения.


    $threads Примечание Время выполнения, секунд
    PHP без ZTS
    1 без pthreads, без polyfill 265.05
    1 polyfill 298.26
    PHP с ZTS
    1 без pthreads, без polyfill 37.65
    1 68.58
    2 26.18
    3 16.87
    4 12.96
    5 12.57
    6 12.07
    7 11.78
    8 11.62

    Из первых двух строк видно, что при использовании polyfill мы потеряли примерно 13% производительности в этом примере, это относительно линейного кода на совсем простом PHP “без всего”.


    Далее, PHP с ZTS. Не обращайте внимание на такую большую разницу во времени выполнения в сравнении с PHP без ZTS (37.65 против 265.05 секунд), я не пытался привести к общему знаменателю настройки PHP. В случае без ZTS у меня включен XDebug например.


    Как видно, при использовании 2-х потоков скорость выполнения программы примерно в 1.5 раза выше, чем в случае с линейным кодом. При использовании 4-х потоков — в 3 раза.


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



    Резюме


    В PHP возможна вполне элегантная работа с многопоточностью с использованием расширения pthreads. Это дает ощутимый прирост производительности.

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 62

      0
      Всё-таки, если порядок обработки заданий не важен, то это не очередь с заданиями, а пул с заданиями. Лучше использовать корректную терминологию.
        0
        Да, исправил.
        +2
        Вопрос от человека, ни разу не работавшего с потоками: есть ли у такого подхода еще какие-то преимущества, помимо производительности?
        • UFO just landed and posted this here
            +1

            Это собственно причина того что производительность улучшается. Но опять же нужно учитывать еще локи, переключение контекста… Словом в WEB оно не столь разумно как event loop в подавляющем большинстве случаев.

            • UFO just landed and posted this here
                0
                всякие выгрузки на PHP и суют их уже в крон.

                а это не сетевое взаимодействие? Там простоев из-за сетевых запросов обычно больше чем CPU-time на работу самого пыха.


                Короче есть где применить, нужно просто иметь ввиду, что есть такая классная штука.

                Иметь в виду — конечно стоит. Но нужно так же знать о других вариантах (мультиплексирование потока выполнения, корутины, очереди + процессы) и выбирать из них. А так для тредов есть свои задачи.

            +1

            У каждого подхода свои преимущества и недостатки. Подходы:


            • Создаем общедоступную очередь, например, на Beanstalk, RabbitMQ или Redis или еще на чем-нибудь. Создаем PHP скрипт, который будем запускать из консоли несколько раз, создавая нужное количество процессов. Это решение наиболее универсальное.
              • Плюсы. Хорошая масштабируемость на несколько серверов, отказоустойчивость.
              • Минусы. Может быть неудобно или непрозрачно с точки зрения архитектуры. Если в обработке данных несколько “узких горлышек”, то возможно, понадобится предусмотреть несколько очередей.
            • Создавать потоки через Curl, для такого решения есть даже проект на гитхабе.
              • Плюсы. Мне неизвестны.
              • Минусы. Ненадежно.
            • Использовать popen().
              • Плюсы. Просто с первого взгляда.
              • Минусы. Сложно организовать равномерную загрузку ядер. Трудности в создании общей очереди.
            • Написать собственное расширение для PHP и пользоваться им.
              • Плюсы. Можно сделать полный фен шуй.
              • Минусы. Затратно.
            • Воспользоваться расширением PCNTL. Насколько это удачное решение, возможно, кто-то расскажет в комментариях.
            • Воспользоваться готовым расширением pthreads.
              • Плюсы. Надежность. Можно прятать многопоточное поведение внутри модуля, не выносить на уровень архитектуры. Простота в создании общей очереди.
              • Минусы. Нельзя масштабировать на несколько серверов.
              +1
              Благодарю за развернутый ответ!
                +1

                Все кроме последнего никакого отношения к потокам не имеет. Это порождение процессов.


                Минусы. Сложно организовать равномерную загрузку ядер. Трудности в создании общей очереди.

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


                Написать собственное расширение для PHP и пользоваться им.

                Все уже написано. Имеет смысл только написало аналог микротредов (корутины + пул тредов), но в теории это можно и на userland сделать.


                Воспользоваться расширением PCNTL

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


                Так же есть отдельные экстеншены для того что бы организовать общую память между процессами, так что можно добиться прикольных вещей имея при этом свое адресное пространство для каждого процесса (безопаснее) + немного общей для кэшей. Правда тут уже нужно опять же вводи локи или разбираться с lock-free программированием и тут я не уверен что это можно делать красиво в php.


                Воспользоваться готовым расширением pthreads.

                На самом деле для WEB в 95% случаев все упирается в эффективность работы с I/O и тут явный лидер корутины/event loop так как нет накладных расходов на переключение контекстов. А что бы эффективнее использовать ресурсы можно просто увеличить количество процессов.


                Треды хорошо подходят для каких-то массивных вычислений, хотя тут уже вопрос зачем нам PHP если мы можем написать многопоточную програмку на Си с векторизацией вычислений и получить 100x профита.


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

                  +1
                  хотя тут уже вопрос зачем нам PHP если мы можем написать многопоточную програмку на Си с векторизацией вычислений и получить 100x профита

                  Ну например если весь проект на PHP, то зачем для одной задачи искать программера на Си. На первое время решения на PHP хватит с головой, тем более, если речь о PHP 7.
                    0
                    На первое время решения на PHP хватит с головой, тем более, если речь о PHP 7.

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


                    Если алгоритм легко паралелится — то никаких проблем но "первое время" может быстро закончится если мы зависим от количества данных и оно увиличивается. У меня был на проектике скриптик с k-means, написанный на коленке потому что так быстрее. Его "первое время" закончилось через 2 недели, когда обработка данных стала занимать по 5 минут на запуск (100КК итераций). Переход на PHP7 а потом на HHVM снизил время в 2 раза но с объемами данных этот "профит" быстро бы невилировался. Распаралелить его обошлось бы довольно дорого, в итоге просто применили другой алгоритм кластеризации реализованный на java (потому что готовый и потому что реализовывать его на PHP сильно дорого вышло бы).

                    +1
                    Скажем так, у меня была такая задача, реализована на PCNTL, суть заключалась в том, что регулярно поступает достаточно большой набор ссылок на картинки для каталога товаров из импорта и требуется произвести конвертацию в формат сайта. Деление на задачи производится по домену и у каждого запускается свой пул потоков в соответствии с настройками. В рамках нагрузки на систему, каждый поток незначительный, но требуется обработать максимум в кратчайшие сроки. На мой взгляд дешевле и быстрее такое сделать на PHP, чем на Си.

                    Если использовать расширение pthreads + PCNTL, то можно сократить количество процессов и выиграть в производительности
                      0
                      На мой взгляд дешевле и быстрее такое сделать на PHP, чем на Си.

                      На мой взгляд вам в вашей задаче потоки не нужны. Берем очередь, берем парочку процессов-воркеров обрабатывающих очередь, в каждом воркере будут крутиться корутины/event loop (amphp, reactphp, recoil). Итого имеет малое количество процессов, отсутствие оверхэда на создание потоков/переключение контекста, отсутствие блокировок, максимальную утилизацию CPU, максимальный перформанс. Ну и делать это даже проще чем на тредах.

                +1
                А если например нужно использовать mysqli?
                На пример: задача в том, чтобы в несколько потоков брать записи из одной таблицы, проводить манипуляции над данными, а результат записывать в другую таблицу в произвольном порядке?
                При этом потоки не должны читать одну и ту-же запись, но должны гарантированно прочитать все записи.
                Объект mysqli нельзя «расшаривать» между потоками.
                  0

                  Не пробовал, но в этом примере должно быть подходящее решение

                    0
                    1. создаёте mysqli объект после разделения на потоки — у каждого потока будет своё подключение.
                    2. делаете очередь в базе, там дополнительные столбцы: «бот который взял на обработку».
                    в момент взятия задачи на обработку, делаете лок:
                    update table set bot=BOT_ID where bot=0;
                    

                    3. В добавок, нужно сделать какой нибуть механизм, который будет разлочивать строки ботов которые зависли.
                      0
                      Я вместо этого делал так: каждый поток читает все строки таблицы, но обрабатывает только те, которые должен.
                      Ну там несложные вычисления: каждый поток знает свой номер и общее количество потоков, ну и отсчитывает каждую X строчку со сдвигом Y
                        0
                        Делал примерно так же, но присваивал значение timestamp. И отдельным потоком просматривал все записи с значением меньше, чем «timestamp — время на таймаут», если такие появлялись — давал им еще несколько попыток обнуляя значение, затем блокировка записи с присвоением значения заведомо бОльшим.
                          0
                          У меня было реализовано так, по некому группирующему параметру из источника данных создавалась задача, в отдельной таблице. Далее, при чтении из источника, полученная строка сразу же удалялась. Одновременно может работать любое количество потоков без пересечения. Я делал это на Redis, там своя специфика работы на сокетах и как такового persistent connection просто не существует. В MySQL таких проблем нет.
                          Если не удалять записи, а устанавливать некий параметр, аля bot_id, то всё равно нужно как-то чистить обработанные записи, чтобы не перегружать таблицу.
                            0
                            В вашем случае, есть возможность что задача будет удалена, хотя она не была выполнена (Т.е. создаём задачу, бот её сразу же забирает, и потом бот почему то падает.). Лучше удалять задачу после того как она взята и выполнена. Т.е. когда «берём задачу» — её лочим, но не удаляем. Когда сделали — удаляем.
                        +1
                        включен XDebug например

                        Замерам и выводам тогда не стоит верить. XDebug искажает картину полностью.

                          0

                          Основные сравнения тут без XDebug. С XDebug только 2 довольно синтетических теста для оценки потерь от использования polyfill.

                            0

                            Разве не для всех замеров по PHP с ZTS он был включен?

                              +1

                              Был включен только для PHP без ZTS

                                +1

                                Ох, заработался...

                          +3
                          Воспользоваться расширением PCNTL. Насколько это удачное решение, возможно, кто-то расскажет в комментариях.

                          Это вполне себе удачное решение. Только вы должны понимать, что pcntl — это многопроцессность, а не многопоточность.

                          Плюсы:
                          • Процессы независимы, каждый из них выполняется изолированно, его время жизни никак не зависит от других процессов
                          • Расширение pcntl работает везде и из коробки (кроме Windows по понятным причинам)


                          Минусы:
                          • Форк — не самая дешевая операция
                          • N процессов требуют *N памяти
                          • Межпроцессное взаимодействие вам нужно выстраивать самостоятельно
                          • После форка дочерний процесс теряет контекст (подключения к файлам, БД, прочим ресурсам), его нужно восстанавливать


                          В целом мне pcntl нравится и он находит своё применение
                            +3
                            Похоже, это связано с тем, что физических ядра у моего процессора 4

                            так и есть, все рекомендации сводятся к тому, чтоб запускать по одному потоку на ядро
                              0

                              Это связано с переключением контекста. Чем больше потоков, тем чаще нам нужно переключаться, а операция эта не дешевая.

                                0
                                Всё зависит от того, чем потоки занимаются. У меня сейчас на 8 ядрах запущено 2600+ потоков и всё летает. А если 8 потоков заняты вычислениями, загружая каждое ядро на 100%, то, естественно, что не делай, они не начнут быстрее выполнять свою работу.
                                –1
                                к сожалению это не применимо для работы с БД
                                  +1
                                  Почему? Даже если открыть N коннектов и работать с каждыми из них по отдельности?
                                    0

                                    Приминимо, просто нужно держать не один коннекшен к базе, а пул коннекшенов.

                                      0
                                      Проверял, точно не падает?
                                      когда тестировал, у меня упало…

                                      используя неблокируемое соединение — не обязательно делать несколько воркеров,
                                      тут совсем другой код…

                                      если кто и проверял — путь выложат код в студию…
                                        +1
                                        точно не падает?

                                        почему не падает? Падает. Только в моем случае из-за базы не падало.


                                        используя неблокируемое соединение — не обязательно делать несколько воркеров

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


                                        если кто и проверял — путь выложат код в студию…

                                        Вам код чего? Ну мол примерно что бы понимать минимальный набор функционала что бы убедиться что… ну не ок все а можно хотя бы пробовать.

                                          0
                                          > что бы убедиться что…
                                          параллельное выполнение запросов в БД
                                      +3
                                      К счастью с базой можно работать в несколько потоков еще в php5 — можно использовать mysqli с его неблокирующими запросами к БД.
                                      –2
                                      Бесспорно полезное расширение, однако надо всегда держать в уме, что PHP интерпретируемый язык, который итак имеет достаточно накладных расходов на выполнение своих скиптов. Так что распараллеливание может и дать достаточное ускорение работы, но при этом забрать существенно больше ресурсов чем ожидается, так как все обертка на posix threads все же будет добавлять некий оверхед. Так же, если есть нужна проводить тяжелые операции на сервере (которые требуют оптимизации распараллеливанием), то это повод задуматься как это в будущем будет развиваться и, возможно, стоит какую то часть функционала переписать в виде C расширения, например.
                                        0
                                        так как все обертка на posix threads все же будет добавлять некий оверхед

                                        Незначительный, им можно принебречь. Но в остальном согласен, event loop справляется лучше, а если нужно нагрузить все ядра — просто делаем больше процессов.

                                        0
                                        Все бы хорошо, если не большое количчество нюансов, ограничей и отличия работы PHP c pthreads от PHP как такового.
                                        Нельзя быть уверенным, что стандартная языковая конструкция будет работать корректно.
                                        Пример: https://github.com/krakjoe/pthreads/issues/52
                                        И многое другое, типа позднего статического связываня и наследования… Достаточно взглянуть https://github.com/krakjoe/pthreads/issues

                                        Как эксперимент, очень интересная библиотека. В продакшен?… врядли.
                                          0
                                          Промахнулся со статическим связыванием, проблема со статическими свойствами.
                                          +3

                                          pthreads хорош, но стоит упомянуть и про подводные камни.


                                          Есть возможности "пронаследовать" какую-то часть окружения в поток (причём, по умолчанию это не "ничего"), но в ряде случаев вылезают WTF. Например, если в мастере был подключен автозагрузчик композера через require_once, он не подключится в потоке аналогичном образом.


                                          Ресурсы, файловые дескрипторы и прочее не шарятся. Потому лучше стараться запускать потоки через PTHREADS_INHERIT_NONE, а внутри производить собственные подключения к БД, логи (монолог умеет писать с блокировками) и т.д.


                                          Общение между потоком и мастер-процессом происходит с чем-то вроде сериализации, потому вы не сможете вернуть в мастер Closure, т.к. тот не сериализуем. Есть особенности и с другими типами данных, например с массивами.


                                          Если вы захотите вернуть из потока вложенный массив и сделаете так.


                                          $this->result = ['hello' => ['foo' => 'bar']]

                                          То можете словить ошибку, так как это будет преобразовано в Volatile-объекты и при попытке считать данные в мастере они будут уже уничтожены сборщиком мусора.


                                          Самый простой "способ" в таком случае, это явно приводить к массиву:


                                          $this->result = (array) ['hello' => ['foo' => 'bar']]

                                          Подробнее здесь: http://stackoverflow.com/questions/14796674/a-php-pthreads-thread-class-cant-use-array


                                          В целом, от меня общий совет — каждый поток должен быть максимально независим от мастер процесса. Обмен, по возможности, производить скалярными данными.


                                          Также на некоторых конфигурациях систем (например у меня такое происходит Debian 7 и pthreads 3) могут вылетать ошибки сегментирования. С чем это точно связано я не знаю, но скорее всего с версиями каких-то библиотек.

                                            0
                                            Молодец Николай, редкую тему поднял (в контексте php/pthreads) и с конкретными примерами — привет от бывшего коллеги.
                                              +1
                                              Коля, я бы добавил на графики шкалу времени, чтобы был понятен масштаб. Вообще, это типичный вопрос по графикам — что отложено по осям :)
                                                0

                                                Да, можно было. Там по горизонтали секунды, вертикальное деление — это 10 секунд. По вертикали % загрузки.
                                                image

                                                0
                                                Невероятно! Надо бы им на досуге интро на php.net перевести в качестве благодарности…
                                                  0
                                                  Судя по графикам у меня создалось впечатление, что это все равно не мультипоточность, а мультипроцессовость, которые в PHP почему-то постоянно путают.
                                                    0

                                                    Автор расширения пишет, что именно многопоточность.


                                                    This project provides multi-threading that is compatible with PHP based on Posix Threads.
                                                      0

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

                                                        0
                                                        Тут речь как раз, именно, о multi-threading — основанной на самой популярной в *nix/C реализации Posix Threads.

                                                        На сколько мне память не изменяет — потоки создаются в контексте процесса и шарят память между собой.
                                                        То есть, запустили браузер — пошел процесс, а в нем уже threads.
                                                        Разница есть и она существенная, иначе парадигма существования thread-ов была бы обречена.

                                                        Так же есть отличные возможности lock/unlock мониторов (по-крайней мере в С), то есть anti-deadlock механики, ожидания завершений других потоков и т.д…
                                                        0
                                                        Пришел в голову один вариант использования, подскажите, пожалуйста, возможно использовать многопоточность в данном случае или нет?

                                                        Во многих проектах при определенном действии нужно отправить уведомление через email или SMS через какой-то сервис. Тут либо все делать синхронно, либо использовать очереди (Beanstalkd, Amazon SQS и т.д.). Можно ли вместо этого использовать отдельный поток, который отправит все необходимое, а основной поток вернет сообщение об успешной операции?
                                                          +1
                                                          а основной поток вернет сообщение об успешной операции?


                                                          так это ж дожидаться надо, что собственно не сильно эффективнее просто синхронного вызова. Поток будет создан то в контексте обработки одного запроса, а потому наиболее эффективным вариантом будет организовать ивент луп в отдельном процессе воркере который будет забирать задачи из beanstalkd.
                                                            0
                                                            Я имел в виду, что основной поток просто даст сигнал на отправку уведомлений и сразу займется другим делом, не дожидаясь пока письмо и СМС будут успешно (или неуспешно) отправлены.
                                                              0

                                                              Повторюсь. Основная проблема — умирающая модель выполнения пыха. После окончания обработки запроса процесс умрет а вместе с ним и треды. Если же у вас используется какой php-pm или reactphp это вполне себе осуществимо.


                                                              к слову в Symfony так осуществляется отправка email-ов. Вместо того что бы сразу его отправлять задача попадает в очередь (просто массивчик) и после того как респонс ушел на клиент, отправляется сообщение через SAPI о том что больше данных не будет поступать на клиент, соединение закрывается, и мы начинаем отправку почты. В итоге суммарное время выполнения скрипта такое же, но время обработки респонса меньше.

                                                          0
                                                          C:\test\pthreads>php index2.php (without pthreads)
                                                          0.31901907920837

                                                          C:\test\pthreads>php index.php (with pthreads)
                                                          2.0081150531769

                                                          C:\test\pthreads>php -v
                                                          PHP 7.0.13 (cli) (built: Nov 8 2016 13:33:54) ( ZTS )
                                                          Copyright © 1997-2016 The PHP Group
                                                          Zend Engine v3.0.0, Copyright © 1998-2016 Zend Technologies
                                                          with Zend OPcache v7.0.13, Copyright © 1999-2016, by Zend Technologies
                                                            0

                                                            Using polyfill?

                                                              0
                                                              no, pecl pthreads
                                                              +1

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

                                                              0
                                                              Я правильно понял, что можно создать любое количество потоков, а не только количество равное ядрам процессора? То есть это как pcntl, только создаются не отдельные процессы, а потоки?
                                                                +1

                                                                Правильно.

                                                                  0
                                                                  Благодарю.
                                                                +1
                                                                Статья помогла справиться с пулами, спасибо.

                                                                Only users with full accounts can post comments. Log in, please.