PHP Fibers в 2026 году: асинхронное программирование без ReactPHP и сторонних фреймворков
Введение: история асинхронности в PHP
PHP создавался как синхронный язык для обработки HTTP-запросов: один запрос — один поток — один ответ. Долгое время асинхронное программирование на PHP ассоциировалось исключительно со сторонними решениями: ReactPHP, Amp, Swoole или RoadRunner. Эти инструменты прекрасно справлялись со своими задачами, но требовали изучения чужих абстракций, нетривиальной настройки окружения и отказа от привычных синхронных библиотек.
В PHP 8.1 (ноябрь 2021 года) появился механизм Fibers — примитив кооперативной многозадачности, встроенный непосредственно в ядро языка. К 2024–2026 годам экосистема вокруг Fibers заметно созрела: улучшились инструменты отладки, появились стабильные абстракции поверх голого API, а фреймворк Laravel начал использовать Fibers в своих внутренних механизмах. В этой статье мы разберём, как работают Fibers, научимся писать конкурентный код без сторонних зависимостей и поймём, где у этого подхода границы применимости.
Что такое Fiber: модель выполнения
Fiber (волокно) — это облегчённый механизм кооперативного переключения контекста выполнения внутри одного PHP-процесса. В отличие от системных потоков (threads), Fibers не выполняются параллельно: в каждый момент времени активно только одно волокно. Переключение происходит явно — через вызов Fiber::suspend().
Разберём ключевые отличия от смежных концепций:
- Генераторы (Generators) — тоже позволяют приостанавливать выполнение функции через
yield, но не умеют передавать управление произвольному стороннему коду за пределами генератора. Fiber может быть приостановлен из любого места стека вызовов. - Корутины (Coroutines) — концептуально близки к Fibers; в PHP Fibers фактически являются полноценными симметричными корутинами с отдельным стеком вызовов.
- Threads (pthreads/parallel) — настоящий параллелизм с отдельными стеками памяти и блокировками. Fibers работают в одном потоке и не требуют синхронизации разделяемой памяти.
- async/await (как в JS/Dart) — Fibers являются низкоуровневым строительным блоком, поверх которого можно реализовать синтаксический сахар async/await.
Жизненный цикл Fiber проходит четыре состояния: created → running → suspended → terminated. Fiber запускается вызовом start(), приостанавливается изнутри через suspend(), возобновляется снаружи через resume() и завершается, когда callback-функция заканчивает выполнение.
API Fibers: полный разбор с примерами
Базовый пример
<?php
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('first suspension');
echo "Resumed with: {$value}\n";
Fiber::suspend('second suspension');
echo "Fiber completed\n";
});
// Запускаем Fiber; выполнение идёт до первого suspend()
$result1 = $fiber->start();
echo "Suspended with: {$result1}\n"; // "first suspension"
// Возобновляем, передавая значение внутрь Fiber
$result2 = $fiber->resume('hello');
echo "Suspended with: {$result2}\n"; // "second suspension"
// Завершаем
$fiber->resume();
echo "Is terminated: " . ($fiber->isTerminated() ? 'yes' : 'no') . "\n";
Метод Fiber::suspend($value) вызывается изнутри волокна и передаёт значение наружу (оно становится возвращаемым значением start() или resume()). Значение, переданное в resume($value) снаружи, становится возвращаемым значением Fiber::suspend() внутри волокна.
Получение результата через getReturn()
<?php
$fiber = new Fiber(function (): int {
Fiber::suspend();
return 42;
});
$fiber->start();
$fiber->resume();
if ($fiber->isTerminated()) {
echo $fiber->getReturn(); // 42
}
Метод getReturn() бросает исключение FiberError, если волокно ещё не завершено — это важно учитывать при построении планировщиков.
Практические паттерны
Простой планировщик задач на Fibers
Центральный паттерн при работе с Fibers — кооперативный планировщик (cooperative scheduler). Он хранит очередь волокон и поочерёдно возобновляет их, пока все не завершатся.
<?php
class FiberScheduler
{
/** @var Fiber[] */
private array $queue = [];
public function add(callable $callback): void
{
$this->queue[] = new Fiber($callback);
}
public function run(): void
{
// Стартуем все волокна
foreach ($this->queue as $fiber) {
$fiber->start();
}
// Продолжаем крутить цикл, пока есть приостановленные волокна
while (true) {
$active = array_filter(
$this->queue,
fn(Fiber $f) => $f->isSuspended()
);
if (empty($active)) {
break;
}
foreach ($active as $fiber) {
$fiber->resume();
}
}
}
}
// Использование
$scheduler = new FiberScheduler();
$scheduler->add(function (): void {
echo "Task 1: step A\n";
Fiber::suspend();
echo "Task 1: step B\n";
Fiber::suspend();
echo "Task 1: step C\n";
});
$scheduler->add(function (): void {
echo "Task 2: step A\n";
Fiber::suspend();
echo "Task 2: step B\n";
});
$scheduler->run();
// Task 1: step A
// Task 2: step A
// Task 1: step B
// Task 2: step B
// Task 1: step C
Конкурентные HTTP-запросы с curl_multi и Fibers
Настоящая польза от Fibers проявляется при интеграции с неблокирующим I/O. Ниже — пример конкурентных HTTP-запросов через curl_multi без сторонних библиотек:
<?php
function asyncGet(string $url): Fiber
{
return new Fiber(function () use ($url): string {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch);
do {
$status = curl_multi_exec($mh, $running);
if ($running) {
curl_multi_select($mh, 0.01); // неблокирующий select
Fiber::suspend(); // отдаём управление планировщику
}
} while ($running && $status === CURLM_OK);
$response = curl_multi_getcontent($ch);
curl_multi_remove_handle($mh, $ch);
curl_multi_close($mh);
curl_close($ch);
return $response;
});
}
$urls = [
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/2',
'https://httpbin.org/uuid',
];
$fibers = array_map('asyncGet', $urls);
// Стартуем все волокна
foreach ($fibers as $fiber) {
$fiber->start();
}
// Крутим цикл событий
while (array_filter($fibers, fn($f) => !$f->isTerminated())) {
foreach ($fibers as $fiber) {
if ($fiber->isSuspended()) {
$fiber->resume();
}
}
}
// Собираем результаты
foreach ($fibers as $i => $fiber) {
$body = $fiber->getReturn();
echo "Response {$i}: " . substr($body, 0, 80) . "...\n";
}
Все три запроса выполняются конкурентно в рамках одного PHP-процесса без каких-либо расширений, кроме встроенного curl.
Интеграция Fibers с Laravel
Начиная с Laravel 9 (вышедшего одновременно с PHP 8.1), фреймворк постепенно интегрирует Fibers в свои механизмы. К 2026 году наиболее важные точки интеграции выглядят следующим образом:
- Laravel Concurrency — пакет
laravel/concurrency, представленный в Laravel 11, предоставляет фасадConcurrency::run(), который под капотом использует Fibers для конкурентного выполнения замыканий в рамках одного воркера. - Queue Workers — Horizon и встроенный воркер очередей опционально используют Fibers для обработки нескольких заданий без запуска дополнительных процессов.
- HTTP Client —
Http::pool()в Laravel использует Guzzle promises, которые при наличии Fibers могут быть переведены на кооперативную модель.
<?php
use Illuminate\Support\Facades\Concurrency;
// Laravel 11+: конкурентное выполнение трёх задач
[$users, $orders, $stats] = Concurrency::run([
fn() => DB::table('users')->count(),
fn() => DB::table('orders')->where('status', 'pending')->get(),
fn() => Cache::remember('stats', 60, fn() => computeStats()),
]);
echo "Users: {$users}, Pending orders: " . $orders->count();
Если вы хотите расширить поведение и написать собственный планировщик поверх Laravel, удобно зарегистрировать его в сервис-контейнере и инжектировать через конструктор. Fibers прекрасно работают вместе с DI-контейнером Laravel, поскольку не требуют изменения глобального состояния фреймворка.
Производительность: Fibers vs синхронный код vs Swoole
Важно чётко понимать, что Fibers не ускоряют CPU-bound задачи. Выигрыш достигается исключительно на I/O-bound операциях — сетевых запросах, обращениях к базе данных, чтении файлов — за счёт перекрытия времени ожидания.
Ориентировочные результаты для 100 HTTP-запросов к внешнему API (задержка ~200ms каждый):
- Синхронный PHP — ~20 секунд (запросы последовательны).
- PHP Fibers + curl_multi — ~0.8–1.2 секунды (конкурентно, один процесс).
- Swoole Coroutines — ~0.5–0.7 секунды (нативные корутины с event loop на C).
- ReactPHP — ~0.6–0.9 секунды (асинхронные промисы поверх libuv/event).
Fibers проигрывают Swoole в задачах с тысячами одновременных соединений, потому что event loop у Swoole написан на C и оптимизирован на уровне ядра. Однако для большинства бизнес-приложений разница несущественна, а преимущество Fibers — нулевые зависимости и полная совместимость со стандартным PHP FPM-окружением.
Ограничения и когда Fibers не подходят
Fibers — мощный инструмент, но не серебряная пуля. Вот ситуации, когда лучше выбрать другой подход:
- CPU-intensive вычисления — шифрование, рендеринг изображений, парсинг больших файлов. Здесь нужен настоящий параллелизм:
parallel-расширение или вынесение задачи в отдельный процесс/сервис. - Блокирующие расширения — если вы используете синхронные PDO, mysqli,
file_get_contentsбез потоковых обёрток, Fiber будет заблокирован на I/O так же, как обычный код. Fibers не делают блокирующий код неблокирующим автоматически. - Сложная отладка — стек вызовов при работе с Fibers может быть нелинейным, что затрудняет трассировку ошибок в Xdebug. К 2026 году ситуация улучшилась, но по-прежнему требует внимания.
- Статический анализ — PHPStan и Psalm поддерживают Fibers, но типизация сигнатур
suspend/resumeостаётся неочевидной для junior-разработчиков. - Легаси-библиотеки — если сторонняя библиотека использует
register_shutdown_functionили глобальные обработчики ошибок, поведение внутри Fiber может быть неожиданным.
Перспективы: что ждёт асинхронный PHP в 2026 году
PHP продолжает развиваться в сторону более удобного конкурентного программирования. Несколько актуальных тенденций к 2026 году:
- PHP 8.4 и далее — в RFC-трекере обсуждаются нативный синтаксис
async/awaitповерх Fibers, улучшенный планировщик в стандартной библиотеке и встроенный event loop на уровне ядра. - Amphp v3 — библиотека Amp полностью переписана на Fibers и предоставляет богатый набор примитивов (Channel, DeferredFuture, EventLoop) без необходимости устанавливать Swoole.
- Laravel Reverb и WebSockets — официальный WebSocket-сервер Laravel Reverb использует Fibers под капотом, демонстрируя, что экосистема готова к продакшн-применению.
- FrankenPHP — современный PHP-сервер на Go с поддержкой worker-режима, где Fibers становятся ключевым инструментом для обработки нескольких запросов в одном воркере.
- Стандартизация примитивов — в PHP-сообществе набирает силу дискуссия о включении базового планировщика и примитивов синхронизации (Mutex, Channel) в SPL.
«Fibers — это не замена Swoole для highload. Это способ написать понятный конкурентный код на стандартном PHP, который работает на любом хостинге без дополнительных расширений.» — распространённый консенсус PHP-сообщества к 2025–2026 годам.
Заключение
PHP Fibers, появившиеся в версии 8.1, к 2026 году превратились из экспериментальной возможности в зрелый инструмент, пригодный для продакшн-использования. Они позволяют реализовать кооперативную многозадачность без ReactPHP, Swoole и других тяжёлых зависимостей — только на стандартном PHP с его встроенными расширениями.
Ключевые выводы статьи:
- Fibers — это кооперативные корутины с собственным стеком вызовов; они не создают параллелизма, но устраняют простои на I/O.
- API прост:
new Fiber(callable),start(),Fiber::suspend(),resume(),getReturn(). - Комбинация Fibers +
curl_multiдаёт реальный прирост производительности на сетевых задачах в 10–20 раз по сравнению с последовательным кодом. - Laravel активно интегрирует Fibers через фасад
Concurrencyи серверные компоненты. - Fibers не подходят для CPU-bound задач и не делают блокирующий I/O неблокирующим автоматически.
Если вы PHP-разработчик и ещё не включили Fibers в свой арсенал — самое время начать. Стандартный PHP способен на гораздо большее, чем принято думать.
Технологии
Теги
Руслан Исмаилов
Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →