0Pricing
PHP Academy · Урок

Волокна PHP 8.1

Приостанавливайте и возобновляйте выполнение с помощью встроенных волокон

«Волокна PHP 8.1» — бесплатный урок PHP Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.

Что дают волокна

В PHP 8.1 появились волокна: низкоуровневый примитив ядра, позволяющий приостановить выполняющийся стек вызовов и возобновить его позже из совершенно другого места. Волокно — это полный независимый стек, который можно приостановить в произвольной точке и продолжить без разматывания стека. Это недостающий элемент, позволяющий библиотекам заменить цепочки обещаний синхронно выглядящим await — основой современных API ReactPHP и Amp.

Ваше первое волокно

Создайте волокно с обратным вызовом, запустите его с помощью start() и позвольте ему самостоятельно выполнить suspend(). В точке приостановки управление возвращается вызывающему коду; resume() продолжает выполнение именно с этого места.

<?php
$fiber = new Fiber(function (): void {
    echo "A: inside fiber\n";
    $received = Fiber::suspend('paused');
    echo "C: resumed with '$received'\n";
});

$value = $fiber->start();          // prints A, returns 'paused'
echo "B: fiber suspended, got '$value'\n";
$fiber->resume('hello');           // prints C
echo "D: fiber done\n";

Четыре основных метода

API невелик:

  • $fiber->start(...$args) — начинает выполнение и возвращает первое значение приостановки.
  • Fiber::suspend($value) — приостанавливает текущее волокно, передавая $value возобновляющему его коду.
  • $fiber->resume($value) — продолжает выполнение; $value становится результатом вызова suspend().
  • $fiber->getReturn() — возвращаемое значение обратного вызова после завершения.

Значения передаются через границу приостановки в обоих направлениях.

Двунаправленная передача значений

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

<?php
$fiber = new Fiber(function (): int {
    $a = Fiber::suspend('need first number');
    $b = Fiber::suspend('need second number');
    return $a + $b;
});

$fiber->start();        // suspends: 'need first number'
$fiber->resume(10);     // a = 10, suspends: 'need second number'
$fiber->resume(32);     // b = 32, fiber returns
echo $fiber->getReturn(), PHP_EOL; // 42

Состояние и анализ волокна

Вы можете узнать состояние жизненного цикла волокна. Вызов resume() для завершённого волокна вызывает FiberError, поэтому при создании планировщика используйте эти проверки.

<?php
$fiber = new Fiber(function () { Fiber::suspend(); });
var_dump($fiber->isStarted());     // false
$fiber->start();
var_dump($fiber->isStarted());     // true
var_dump($fiber->isSuspended());   // true
var_dump($fiber->isRunning());     // false (we're outside it)
$fiber->resume();
var_dump($fiber->isTerminated());  // true

Создание простого планировщика

Волокна плюс очередь дают кооперативный планировщик. Каждая задача приостанавливается, чтобы передать управление; планировщик по очереди запускает задачи, пока все они не завершатся. Это каркас, лежащий в основе любой асинхронной среды выполнения.

<?php
$tasks = [];
foreach (['X', 'Y', 'Z'] as $name) {
    $tasks[] = new Fiber(function () use ($name) {
        for ($i = 1; $i <= 2; $i++) {
            echo "$name step $i\n";
            Fiber::suspend();   // give others a turn
        }
    });
}
foreach ($tasks as $t) { $t->start(); }
while ($tasks) {
    foreach ($tasks as $k => $t) {
        if ($t->isTerminated()) { unset($tasks[$k]); continue; }
        $t->resume();
    }
}

Исключения через границы

throwInto() (в API этот вызов называется throw(), как в $fiber->throw()) внедряет исключение в точку приостановки — так цикл сообщает об ошибке ввода-вывода ожидающей задаче. Исключение возникает именно там, где волокно было приостановлено.

<?php
$fiber = new Fiber(function () {
    try {
        Fiber::suspend();
    } catch (RuntimeException $e) {
        echo 'caught: ' . $e->getMessage() . PHP_EOL;
    }
});
$fiber->start();
$fiber->throw(new RuntimeException('io failed')); // prints: caught: io failed

Волокна и генераторы

Генераторы (yield) тоже приостанавливаются, но только на уровне внешней функции. Чтобы выполнять ожидание из вложенного вспомогательного метода с помощью генераторов, нужно передавать yield from вверх по всему стеку, изменяя каждую функцию в цепочке. Волокна приостанавливают весь стек с любой глубины, поэтому глубоко вложенный вызов может выполнить Fiber::suspend(), не сообщая об этом вызывающим его функциям. Поэтому await может быть обычным вызовом функции, а не yield.

<?php
function deep(): string {
    // No yield needed in the call chain — suspends the whole fiber
    return Fiber::suspend('awaiting from deep');
}
$f = new Fiber(fn() => print(deep() . PHP_EOL));
$f->start();
$f->resume('value injected at depth');

Ограничение приостановки

Есть одно жёсткое правило: выполнять Fiber::suspend() можно только внутри волокна. Приостановка из главного потока или через обратный вызов на уровне C, который не поддерживает волокна (некоторые устаревшие компараторы usort, определённые внутренние обратные вызовы), вызывает FiberError: Cannot suspend outside of a fiber. Библиотеки скрывают это ограничение, обеспечивая выполнение всего пользовательского кода внутри волокна, которым управляет цикл.

<?php
try {
    Fiber::suspend(); // main thread — illegal
} catch (FiberError $e) {
    echo $e->getMessage(), PHP_EOL; // Cannot suspend outside of a fiber
}

Как библиотеки используют волокна

В прикладном коде вы редко пишете необработанные волокна. Платформы вроде Amp v3 и ReactPHP (через волокна) оборачивают их: await($promise) внутри вызывает Fiber::suspend(), регистрирует продолжение в цикле событий и возобновляет ваше волокно с помощью resume(), когда операция ввода-вывода завершается. Ваш код выглядит синхронным, а среда выполнения занимается приостановкой и планированием.

<?php
// Conceptual: what an await() helper does under the hood
function await(Promise $p): mixed {
    $fiber = Fiber::getCurrent() ?? throw new Error('await() outside fiber');
    $p->then(fn($v) => $fiber->resume($v),
             fn($e) => $fiber->throw($e));
    return Fiber::suspend(); // parked until the promise settles
}

Нюансы памяти и жизненного цикла

Каждое волокно выделяет собственный стек, поэтому создание сотен тысяч волокон требует памяти (хотя они значительно легче потоков ОС). Волокно, приостановленное навсегда, не будет удалено сборщиком мусора, если на него всё ещё что-то ссылается — это незаметная утечка в долго работающих серверах. Кроме того, обратный вызов волокна с неперехваченным исключением передаёт исключение наружу через вызов resume()/start(), поэтому планировщик должен оборачивать возобновление в конструкцию try/catch.

<?php
$fiber = new Fiber(function () {
    throw new RuntimeException('boom inside fiber');
});
try {
    $fiber->start(); // exception surfaces here, at the resumer
} catch (RuntimeException $e) {
    echo 'scheduler caught: ' . $e->getMessage() . PHP_EOL;
}

Быстрая проверка

Какое ключевое преимущество волокон перед генераторами важно для асинхронности?

Итоги

Волокна — низкоуровневый примитив конкурентности PHP:

  • start(), Fiber::suspend(), resume(), getReturn() приостанавливают и продолжают полный стек вызовов, передавая значения в обоих направлениях.
  • Методы состояния (isSuspended, isTerminated) и throw() позволяют создать кооперативный планировщик и передавать ошибки ввода-вывода.
  • В отличие от генераторов, волокно приостанавливается с любой глубины, что позволяет прозрачно использовать await().
  • Приостанавливать выполнение можно только внутри волокна; платформы вроде Amp и ReactPHP оборачивают волокна, поэтому асинхронный код читается как синхронный.

Часто задаваемые вопросы

Урок «Волокна PHP 8.1» бесплатный?

Да — полный текст урока «Волокна PHP 8.1» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.

Чему я научусь в уроке «Волокна PHP 8.1»?

Приостанавливайте и возобновляйте выполнение с помощью встроенных волокон Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать PHP Academy?

Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Волокна PHP 8.1»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке PHP Academy?

Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Модель конкурентности PHP
  2. Волокна PHP 8.1
  3. Циклы событий с ReactPHP
  4. Высокопроизводительные серверы со Swoole
← Назад к PHP Academy