PHP традиционно считался языком для синхронных скриптов: запрос пришёл, код выполнился, ответ ушёл. Но современные веб-приложения требуют большего — параллельные запросы к внешним API, обработка тысяч соединений и фоновые задачи. В этой статье разберём, как PHP учится быть асинхронным, какие инструменты для этого есть и как не наломать дров.
Почему PHP и асинхронность — это боль?
Представьте, что вы в кафе. Официант (PHP) подходит к столику, принимает заказ и идёт на кухню. Пока повар готовит, официант стоит и ждёт. Именно так работает классический PHP: каждый запрос блокирует процесс до полного выполнения. Если в коде есть запрос к внешнему API, который отвечает 2 секунды, всё это время скрипт просто спит.
В синхронном мире это нормально, когда запросов немного. Но если у вас сотни одновременных пользователей, каждый такой «сон» складывается в огромные очереди. Сервер упирается в лимит процессов, и новые запросы ждут. Асинхронность позволяет официанту принять несколько заказов и отнести их на кухню, не дожидаясь готовности первого блюда.
Главная проблема в том, что PHP изначально проектировался как синхронный язык. Все стандартные функции — file_get_contents, curl_exec, sleep — блокируют выполнение. Чтобы добавить асинхронность, нужно либо использовать расширения (например, Swoole), либо строить событийный цикл на пользовательском коде (ReactPHP, Amp).
Основные подходы к асинхронности в PHP
Есть несколько способов заставить PHP работать асинхронно. Каждый со своими тараканами.
- Многопроцессность (pcntl_fork) — каждый запрос обрабатывается в отдельном дочернем процессе. Просто, но дорого по памяти и не всегда доступно на shared-хостинге.
- Событийный цикл (ReactPHP, Amp) — библиотеки, которые реализуют неблокирующий ввод-вывод через stream_select. Вы пишете код с колбэками или промисами.
- Корутины (Swoole, Fiber) — позволяют писать асинхронный код так, будто он синхронный. Под капотом переключаются контексты выполнения.
- Очереди (RabbitMQ, Redis) — выносим тяжёлые задачи в фоновые воркеры. Это не совсем асинхронность в рамках одного запроса, но часто именно то, что нужно.
Для наглядности сравним два популярных инструмента: ReactPHP и Swoole.
| Критерий | ReactPHP | Swoole |
|---|---|---|
| Установка | Через Composer, без расширений | Требуется установка PHP-расширения |
| Стиль кода | Промисы и колбэки | Корутины, похоже на синхронный код |
| Производительность | Средняя, зависит от event loop | Высокая, компилируется в C |
| Поддержка HTTP/2 | Есть | Есть |
| Порог входа | Низкий | Средний (нужно ставить расширение) |
Пример асинхронного запроса на ReactPHP
Допустим, нам нужно одновременно запросить данные с трёх разных API. Синхронно это займёт суммарное время всех запросов. С ReactPHP мы запустим их параллельно и дождёмся всех результатов.
get($url);
}
React\Promise\all($promises)->then(function ($responses) {
foreach ($responses as $response) {
echo $response->getBody() . PHP_EOL;
}
});
$loop->run();
Здесь мы создаём событийный цикл, инициируем три GET-запроса одновременно и ждём, пока все они завершатся. Время выполнения будет примерно равно самому долгому запросу, а не их сумме.
Корутины в Swoole: асинхронность без боли
Swoole предлагает корутины — это как потоки, но легче. Вы пишете обычный синхронный код, а внутри происходит магия переключения. Например, так выглядит параллельный опрос двух сервисов:
push($result);
});
Coroutine::create(function () use ($channel) {
$result = file_get_contents('https://api.example.com/posts');
$channel->push($result);
});
for ($i = 0; $i < 2; $i++) {
echo $channel->pop() . PHP_EOL;
}
Обратите внимание: мы используем обычный file_get_contents, но внутри корутины он не блокирует другие корутины. Swoole перехватывает блокирующие вызовы и делает их неблокирующими на уровне ядра.
Практические советы и подводные камни
Асинхронность в PHP — это не серебряная пуля. Вот несколько рекомендаций, чтобы не наступить на грабли.
- Не тащите асинхронность везде. Если у вас обычный сайт с низкой нагрузкой, синхронный PHP работает отлично. Асинхронность нужна для высоконагруженных API, websocket-серверов и микросервисов.
- Следите за утечками памяти. В долгоживущих процессах (Swoole, ReactPHP) память не освобождается после каждого запроса, как в классическом PHP. Глобальные переменные и статические массивы могут расти бесконечно.
- Используйте пулы соединений. Асинхронные клиенты к базам данных (MySQL, Redis) должны переиспользовать соединения, иначе вы быстро исчерпаете лимиты.
- Тестируйте под нагрузкой. Асинхронный код сложнее отлаживать. Обязательно пишите юнит-тесты и проводите нагрузочное тестирование (например, с помощью wrk или ab).
Ещё один важный момент: асинхронность не отменяет очереди. Если задача может выполняться минутами (отправка email, генерация отчёта), лучше вынести её в фоновый воркер через RabbitMQ или Redis. Асинхронность в рамках запроса хороша для коротких операций ввода-вывода.
Что в итоге?
PHP давно перестал быть только синхронным языком. С появлением Swoole, ReactPHP и встроенных Fiber у разработчиков есть инструменты для создания высокопроизводительных асинхронных приложений. Но выбирать их стоит осознанно: если у вас нет задач, где нужна параллельная обработка тысяч соединений, возможно, вам достаточно обычного PHP с очередями.
Главное — понимать, как работает событийный цикл, не блокировать его и следить за ресурсами. Тогда асинхронность станет вашим другом, а не источником багов.



