Backend-разработка

PHP Fibers в 2026 году: асинхронное программирование без ReactPHP и сторонних фреймворков

Ruslan Ismailov Опубликовано 12 мин чтения
P

Введение: история асинхронности в 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 проходит четыре состояния: createdrunningsuspendedterminated. 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 ClientHttp::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 с его встроенными расширениями.

Ключевые выводы статьи:

  1. Fibers — это кооперативные корутины с собственным стеком вызовов; они не создают параллелизма, но устраняют простои на I/O.
  2. API прост: new Fiber(callable), start(), Fiber::suspend(), resume(), getReturn().
  3. Комбинация Fibers + curl_multi даёт реальный прирост производительности на сетевых задачах в 10–20 раз по сравнению с последовательным кодом.
  4. Laravel активно интегрирует Fibers через фасад Concurrency и серверные компоненты.
  5. Fibers не подходят для CPU-bound задач и не делают блокирующий I/O неблокирующим автоматически.

Если вы PHP-разработчик и ещё не включили Fibers в свой арсенал — самое время начать. Стандартный PHP способен на гораздо большее, чем принято думать.

Технологии

Теги

Руслан Исмаилов

Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →