Comments 62
Это собственно причина того что производительность улучшается. Но опять же нужно учитывать еще локи, переключение контекста… Словом в WEB оно не столь разумно как event loop в подавляющем большинстве случаев.
всякие выгрузки на PHP и суют их уже в крон.
а это не сетевое взаимодействие? Там простоев из-за сетевых запросов обычно больше чем CPU-time на работу самого пыха.
Короче есть где применить, нужно просто иметь ввиду, что есть такая классная штука.
Иметь в виду — конечно стоит. Но нужно так же знать о других вариантах (мультиплексирование потока выполнения, корутины, очереди + процессы) и выбирать из них. А так для тредов есть свои задачи.
У каждого подхода свои преимущества и недостатки. Подходы:
- Создаем общедоступную очередь, например, на Beanstalk, RabbitMQ или Redis или еще на чем-нибудь. Создаем PHP скрипт, который будем запускать из консоли несколько раз, создавая нужное количество процессов. Это решение наиболее универсальное.
- Плюсы. Хорошая масштабируемость на несколько серверов, отказоустойчивость.
- Минусы. Может быть неудобно или непрозрачно с точки зрения архитектуры. Если в обработке данных несколько “узких горлышек”, то возможно, понадобится предусмотреть несколько очередей.
- Создавать потоки через Curl, для такого решения есть даже проект на гитхабе.
- Плюсы. Мне неизвестны.
- Минусы. Ненадежно.
- Использовать popen().
- Плюсы. Просто с первого взгляда.
- Минусы. Сложно организовать равномерную загрузку ядер. Трудности в создании общей очереди.
- Написать собственное расширение для PHP и пользоваться им.
- Плюсы. Можно сделать полный фен шуй.
- Минусы. Затратно.
- Воспользоваться расширением PCNTL. Насколько это удачное решение, возможно, кто-то расскажет в комментариях.
- Воспользоваться готовым расширением pthreads.
- Плюсы. Надежность. Можно прятать многопоточное поведение внутри модуля, не выносить на уровень архитектуры. Простота в создании общей очереди.
- Минусы. Нельзя масштабировать на несколько серверов.
Все кроме последнего никакого отношения к потокам не имеет. Это порождение процессов.
Минусы. Сложно организовать равномерную загрузку ядер. Трудности в создании общей очереди.
нет никаких проблем, просто мастер процесс должен распределять задачи воркерам, а коркеры должны жить всегда.
Написать собственное расширение для PHP и пользоваться им.
Все уже написано. Имеет смысл только написало аналог микротредов (корутины + пул тредов), но в теории это можно и на userland сделать.
Воспользоваться расширением PCNTL
плюсы: это полный контроль за дочерними процессами, возможность управлять их жизненным циклом, организовывать обмен сигналами. Минус — это всеравно процессы, они жирные, их нужно один раз порадить и держать в пуле, желательно из мастер процесса который только монитори процессы, что-то типа супервизора.
Так же есть отдельные экстеншены для того что бы организовать общую память между процессами, так что можно добиться прикольных вещей имея при этом свое адресное пространство для каждого процесса (безопаснее) + немного общей для кэшей. Правда тут уже нужно опять же вводи локи или разбираться с lock-free программированием и тут я не уверен что это можно делать красиво в php.
Воспользоваться готовым расширением pthreads.
На самом деле для WEB в 95% случаев все упирается в эффективность работы с I/O и тут явный лидер корутины/event loop так как нет накладных расходов на переключение контекстов. А что бы эффективнее использовать ресурсы можно просто увеличить количество процессов.
Треды хорошо подходят для каких-то массивных вычислений, хотя тут уже вопрос зачем нам PHP если мы можем написать многопоточную програмку на Си с векторизацией вычислений и получить 100x профита.
Так что использование тредов в PHP я считаю экзотикой нежели чем-то важным и необходимым. Хотя понимать минусы использования тредов — это важно.
хотя тут уже вопрос зачем нам PHP если мы можем написать многопоточную програмку на Си с векторизацией вычислений и получить 100x профита
Ну например если весь проект на PHP, то зачем для одной задачи искать программера на Си. На первое время решения на PHP хватит с головой, тем более, если речь о PHP 7.
На первое время решения на PHP хватит с головой, тем более, если речь о PHP 7.
Если речь идет про объемные вычисления, они для начала должны легко паралелиться, иначе объем работ на паралелизацию может слихвой покрыть разницу php vs С.
Если алгоритм легко паралелится — то никаких проблем но "первое время" может быстро закончится если мы зависим от количества данных и оно увиличивается. У меня был на проектике скриптик с k-means, написанный на коленке потому что так быстрее. Его "первое время" закончилось через 2 недели, когда обработка данных стала занимать по 5 минут на запуск (100КК итераций). Переход на PHP7 а потом на HHVM снизил время в 2 раза но с объемами данных этот "профит" быстро бы невилировался. Распаралелить его обошлось бы довольно дорого, в итоге просто применили другой алгоритм кластеризации реализованный на java (потому что готовый и потому что реализовывать его на PHP сильно дорого вышло бы).
Если использовать расширение pthreads + PCNTL, то можно сократить количество процессов и выиграть в производительности
На мой взгляд дешевле и быстрее такое сделать на PHP, чем на Си.
На мой взгляд вам в вашей задаче потоки не нужны. Берем очередь, берем парочку процессов-воркеров обрабатывающих очередь, в каждом воркере будут крутиться корутины/event loop (amphp, reactphp, recoil). Итого имеет малое количество процессов, отсутствие оверхэда на создание потоков/переключение контекста, отсутствие блокировок, максимальную утилизацию CPU, максимальный перформанс. Ну и делать это даже проще чем на тредах.
На пример: задача в том, чтобы в несколько потоков брать записи из одной таблицы, проводить манипуляции над данными, а результат записывать в другую таблицу в произвольном порядке?
При этом потоки не должны читать одну и ту-же запись, но должны гарантированно прочитать все записи.
Объект mysqli нельзя «расшаривать» между потоками.
Не пробовал, но в этом примере должно быть подходящее решение
2. делаете очередь в базе, там дополнительные столбцы: «бот который взял на обработку».
в момент взятия задачи на обработку, делаете лок:
update table set bot=BOT_ID where bot=0;
3. В добавок, нужно сделать какой нибуть механизм, который будет разлочивать строки ботов которые зависли.
Ну там несложные вычисления: каждый поток знает свой номер и общее количество потоков, ну и отсчитывает каждую X строчку со сдвигом Y
Если не удалять записи, а устанавливать некий параметр, аля bot_id, то всё равно нужно как-то чистить обработанные записи, чтобы не перегружать таблицу.
включен XDebug например
Замерам и выводам тогда не стоит верить. XDebug искажает картину полностью.
Воспользоваться расширением PCNTL. Насколько это удачное решение, возможно, кто-то расскажет в комментариях.
Это вполне себе удачное решение. Только вы должны понимать, что pcntl — это многопроцессность, а не многопоточность.
Плюсы:
- Процессы независимы, каждый из них выполняется изолированно, его время жизни никак не зависит от других процессов
- Расширение pcntl работает везде и из коробки (кроме Windows по понятным причинам)
Минусы:
- Форк — не самая дешевая операция
- N процессов требуют *N памяти
- Межпроцессное взаимодействие вам нужно выстраивать самостоятельно
- После форка дочерний процесс теряет контекст (подключения к файлам, БД, прочим ресурсам), его нужно восстанавливать
В целом мне pcntl нравится и он находит своё применение
Похоже, это связано с тем, что физических ядра у моего процессора 4
так и есть, все рекомендации сводятся к тому, чтоб запускать по одному потоку на ядро
Это связано с переключением контекста. Чем больше потоков, тем чаще нам нужно переключаться, а операция эта не дешевая.
Приминимо, просто нужно держать не один коннекшен к базе, а пул коннекшенов.
когда тестировал, у меня упало…
используя неблокируемое соединение — не обязательно делать несколько воркеров,
тут совсем другой код…
если кто и проверял — путь выложат код в студию…
точно не падает?
почему не падает? Падает. Только в моем случае из-за базы не падало.
используя неблокируемое соединение — не обязательно делать несколько воркеров
используя треды не обязательно делать пул соединений с базой ибо… ну воркеры всеравно изолированные. Шаред мемори только в явном виде и только то что можно сериализовать.
если кто и проверял — путь выложат код в студию…
Вам код чего? Ну мол примерно что бы понимать минимальный набор функционала что бы убедиться что… ну не ок все а можно хотя бы пробовать.
Нельзя быть уверенным, что стандартная языковая конструкция будет работать корректно.
Пример: https://github.com/krakjoe/pthreads/issues/52
И многое другое, типа позднего статического связываня и наследования… Достаточно взглянуть https://github.com/krakjoe/pthreads/issues
Как эксперимент, очень интересная библиотека. В продакшен?… врядли.
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) могут вылетать ошибки сегментирования. С чем это точно связано я не знаю, но скорее всего с версиями каких-то библиотек.
На самом деле разница между многопроцессностью и многопоточностью проявляется только в общем потреблении ресурсов (потоки меньше жрут, что как бы логично) и в меньшем времени, необходимом на старт. А графики выглядели бы примерно так же. Просто с потоками не нужно париться о организации IPC
На сколько мне память не изменяет — потоки создаются в контексте процесса и шарят память между собой.
То есть, запустили браузер — пошел процесс, а в нем уже threads.
Разница есть и она существенная, иначе парадигма существования thread-ов была бы обречена.
Так же есть отличные возможности lock/unlock мониторов (по-крайней мере в С), то есть anti-deadlock механики, ожидания завершений других потоков и т.д…
Во многих проектах при определенном действии нужно отправить уведомление через email или SMS через какой-то сервис. Тут либо все делать синхронно, либо использовать очереди (Beanstalkd, Amazon SQS и т.д.). Можно ли вместо этого использовать отдельный поток, который отправит все необходимое, а основной поток вернет сообщение об успешной операции?
а основной поток вернет сообщение об успешной операции?
так это ж дожидаться надо, что собственно не сильно эффективнее просто синхронного вызова. Поток будет создан то в контексте обработки одного запроса, а потому наиболее эффективным вариантом будет организовать ивент луп в отдельном процессе воркере который будет забирать задачи из beanstalkd.
Повторюсь. Основная проблема — умирающая модель выполнения пыха. После окончания обработки запроса процесс умрет а вместе с ним и треды. Если же у вас используется какой php-pm или reactphp это вполне себе осуществимо.
к слову в Symfony так осуществляется отправка email-ов. Вместо того что бы сразу его отправлять задача попадает в очередь (просто массивчик) и после того как респонс ушел на клиент, отправляется сообщение через SAPI о том что больше данных не будет поступать на клиент, соединение закрывается, и мы начинаем отправку почты. В итоге суммарное время выполнения скрипта такое же, но время обработки респонса меньше.
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
Многопоточные вычисления в PHP: pthreads