Оригинал: PHP Fibers: How PHP is Finally Warming Up to Asynchronous Programming

Перевод для канала Мы ж программист

Ах, PHP. В течение многих лет он был основным языком для веб-разработки, на нем работали все – от крошечных сайтов-блогов до гигантских платформ вроде Facebook (по крайней мере, на заре своего существования). Но, несмотря на весь свой рост, PHP всегда отличался несколько консервативным подходом к асинхронному программированию – до сих пор. С появлением PHP Fibers в PHP 8.1 (и столь необходимой доработкой в PHP 8.4), кажется, PHP наконец-то принимает радости параллелизма.

Итак, давайте подробно рассмотрим PHP Fibers: что это такое, как они работают и почему вы можете ухмыляться, внедряя их в свою кодовую базу.

Что такое файберы и почему они должны вас волновать?

Думайте о файберах как о легких тредах (threads) – они позволяют приостанавливать и возобновлять работу функций без блокировки всего приложения. Представьте, что вы стоите в очереди в кофейне и заказываете латте. Пока вы ждете, вместо того чтобы просто стоять и ничего не делать, вы можете приостановить заказ и проверить электронную почту или пообщаться с другом. Когда ваш латте будет готов, вы вернетесь к процессу заказа, не пропустив ни одного мгновения.

В коде файберы позволяют вашему приложению продолжать выполнять полезную работу в ожидании завершения трудоемких задач. В отличие от традиционного синхронного PHP-кода, где все выполняется в одну строку (одна команда за другой, без пропусков вперед), файберы позволяют вам перескакивать с одной задачи на другую – идеальный вариант для приложений реального времени, которые не могут позволить себе задерживаться.

Важное замечание о версиях: файберы были введены в PHP 8.1, но они полностью функциональны и более стабильны в PHP 8.4. Так что если вы все еще работаете на PHP 7.x или 8.0, возможно, сейчас самое время обновиться.

Почему именно файберы? Разве в PHP раньше не было асинхронных возможностей?

Вы можете подумать: «Но подождите! Разве у нас не было таких опций, как pcntl_fork и ReactPHP для параллелизма?». И вы будете правы. Но эти методы либо сложны, либо требуют дополнительных расширений и библиотек. Кроме того, они не позволяют писать асинхронный код так естественно, как это делают файберы.

С файберами вам не нужно танцевать с бубном или устанавливать дополнительные расширения. Просто напишите свой код, добавьте немного файберов, и все готово.

Начало работы с файберами в PHP

Теперь давайте поиграем с кодом и узнаем, как работают файберы. Во-первых, давайте проверим, готова ли ваша версия PHP к революции:

Bash
php -v

Если вы видите PHP 8.1 или выше, все готово! Если нет, что ж, пора поговорить с тем, кто управляет вашим сервером, или, в крайнем случае, готовиться к великой миграции.

Базовая анатомия файбера

Красота файберов PHP заключается в их простоте. Вот как создать файбер и запустить его в работу:

PHP
$fiber = new Fiber(function() {
  echo "Fiber started…\n";
  Fiber::suspend(); // Pause the fiber
  echo "Fiber resumed…\n";
});
echo "Fiber starting…\n";
$fiber->start(); // Start the fiber
echo "Main script running…\n";
$fiber->resume(); // Resume the fiber

Вот что происходит:

  1. Мы создаем новый Fiber с функцией, которая делает две вещи: печатает сообщение, приостанавливается, затем печатает другое сообщение.
  2. Мы запускаем файбер, он начинает выполняться до тех пор, пока не достигнет Fiber::suspend().
  3. Файбер «приостанавливает» себя на Fiber::suspend(), позволяя основному скрипту продолжать выполнение.
  4. Позже мы вызываем $fiber->resume(), что подхватывает файбер с того места, где он остановился, и завершает работу.

И вуаля! Вы только что создали асинхронный код в PHP.

Практический пример: Одновременная выборка данных с помощью файберов

Предположим, вы хотите получать данные из нескольких API, не дожидаясь завершения каждого запроса, прежде чем переходить к следующему. Fiber-подход позволяет запускать каждый запрос, приостанавливать его, а затем возобновлять позже, позволяя вашему основному скрипту выполнять другие задачи в это время.

PHP
<?php

// Function to create a fiber that simulates fetching data from an API
function fetchData(string $url, int $delay) {
    return new Fiber(function() use ($url, $delay) {
        echo "Starting request to $url...\n";
        sleep($delay); // Simulate network delay
        Fiber::suspend("Data from $url"); // Pause fiber and return simulated data
    });
}

// Initialize fibers for each "API request"
$fiber1 = fetchData('https://jsonplaceholder.typicode.com/posts/1', 2);
$fiber2 = fetchData('https://jsonplaceholder.typicode.com/posts/2', 3);

// Start fibers (this initiates each "request" and pauses at Fiber::suspend)
$fiber1->start();
$fiber2->start();

echo "Performing other tasks while waiting for data...\n";

// Collect data once fibers have completed their simulated requests
$data1 = $fiber1->resume();
$data2 = $fiber2->resume();

echo "$data1\n";
echo "$data2\n";
echo "All data fetched successfully.\n";

Объяснение:

  1. Создание файберов: Функция fetchData создает файбер для каждого «API-запроса». Каждый файбер имитирует задержку, приостанавливается и возвращает сообщение, как только «данные» будут готовы.
  2. Запуск файберов: start() инициирует каждый файбер, который затем приостанавливается после запуска «запроса», позволяя другому коду работать, пока он «ждет».
  3. Неблокирующее выполнение: После запуска обоих файберов основной скрипт приступает к выполнению других задач (на что указывает оператор echo).
  4. Возобновление работы: Когда вы готовы получить результаты, вы вызываете resume() на каждом файбере, чтобы получить данные. Приостановленные данные из Fiber::suspend() возвращаются при вызове resume(), завершая «запросы».

Сначала это может показаться немного странным, но представьте себе возможности… Вы можете работать над другими частями вашего приложения (например, рендерингом страницы или обработкой пользовательского ввода), пока данные собираются в фоновом режиме.

Реальный пример использования: Асинхронная обработка изображений

Давайте сделаем еще один шаг вперед. Представьте, что у вас есть приложение, в которое пользователи могут загружать изображения, и вы хотите применить к ним несколько фильтров. Обработка каждого фильтра может занять некоторое время, поэтому давайте воспользуемся файберами для обработки всего этого в фоновом режиме:

PHP
<?php

function applyFilter($image, $filter) {
    return new Fiber(function() use ($image, $filter) {
        echo "Applying $filter to $image...\n";
        sleep(2); // Simulate filter processing delay
        Fiber::suspend("$filter applied to $image."); // Suspend fiber and provide completion message
    });
}

$image = 'user-uploaded-image.jpg';
$filters = ['grayscale', 'blur', 'sharpen'];

// Create a fiber for each filter
$fibers = array_map(fn($filter) => applyFilter($image, $filter), $filters);

// Start all fibers
foreach ($fibers as $fiber) {
    $fiber->start();
}

echo "Processing other tasks while filters are being applied...\n";

// Resume each fiber to complete processing and collect results
foreach ($fibers as $fiber) {
    if ($fiber->isSuspended()) {
        $result = $fiber->resume();
        echo "$result\n";
    }
}

echo "All filters have been applied.\n";

В этом примере используются файберы для одновременного применения трех различных фильтров к изображению. Пока фильтры применяются, сценарий может двигаться дальше и «обрабатывать другие задачи». Когда мы будем готовы, мы возобновим работу каждого файбера, чтобы завершить процесс фильтрации.

PHP 8.4 и последующие версии: Что дальше для файберов?

Если файберы в PHP 8.1 были захватывающим началом, то PHP 8.4 предлагает несколько улучшений, которые делают их более стабильными и эффективными для использования в производстве. В версии 8.4 исправлены некоторые проблемы, связанные с управлением памятью и обработкой параллелизма, что делает файберы более предсказуемыми и надежными.

Если вы погружаетесь в асинхронное программирование на PHP, использование PHP 8.4 обеспечит более плавный опыт. Стоит отметить, что файберы являются низкоуровневой функцией – если вы создаете сложные приложения, подумайте об использовании библиотеки более высокого уровня (например, ReactPHP), которая может абстрагировать некоторые детали и предложить дополнительные инструменты для управления параллелизмом.

Предостережение

Файберы – мощный инструмент, но с большой силой приходит и большая ответственность. Легко сойти с ума от файберов и начать приостанавливать и возобновлять все подряд. Но чрезмерное использование может сделать ваш код сложным для чтения и отладки, поэтому используйте файберы с умом. И помните: использование файберов – это как жонглирование горящими мечами: захватывающе, но лучше всего, если вы хорошо знаете основы.

Заключение

PHP Fibers – это отличное дополнение для разработчиков PHP, которые давно хотели найти более простой способ работы с асинхронным программированием. Они предоставляют понятный, управляемый способ работы с параллелизмом, без сложностей потоков или внешних библиотек. От асинхронных вызовов API до фоновой обработки, файберы открывают мир возможностей в PHP, делая его более современным, мощным и просто немного более увлекательным.

Так что, если вы работаете с PHP 8.1 или выше, попробуйте использовать файберы в своем следующем проекте. Возможно, они станут вашей новой любимой функцией!