Конкурентность в PHP: потоки, горутины и async/await — разбор с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему PHP-разработчику нужно думать о конкурентности?

Когда-то давно считалось, что PHP — это язык для «быстрых скриптов», где всё выполняется последовательно, и никакой серьёзной параллельной работы не нужно. Времена изменились. Сегодня мы строим на PHP высоконагруженные API, очереди задач, веб-сокеты и даже микросервисы. И тут встаёт вопрос: как заставить PHP делать несколько дел одновременно, не блокируя всё на свете?

В этой статье мы разберём три подхода к конкурентности в PHP: классические потоки (pthreads), лёгкие горутины (Fiber) и современный синтаксис async/await. Вы увидите реальные примеры кода, узнаете, где какой инструмент применить, и что вообще стоит выбирать в 2025 году.

Если вы ищете параллельное выполнение в PHP для обработки данных, или хотите понять, чем горутины PHP отличаются от потоков — вы по адресу.

1. Многопоточность в PHP: pthreads — старый, но могучий

Что это и как работает?

pthreads — это расширение, которое даёт PHP возможность создавать настоящие потоки ОС. Каждый поток — отдельный экземпляр Zend Engine со своей памятью, но с возможностью обмена данными через Threaded-объекты.

Важный нюанс: pthreads работает только в CLI-режиме и не поддерживается в веб-окружении (Apache/Nginx mod_php). Зато это единственный способ получить реальный параллелизм (а не просто конкурентность) на уровне процессора.

Пример: параллельный парсинг URL

<?php
// Убедитесь, что установлено расширение pthreads
class UrlFetcher extends Thread {
    private $url;
    private $result;

    public function __construct(string $url) {
        $this->url = $url;
    }

    public function run() {
        // Выполняется в отдельном потоке
        $this->result = file_get_contents($this->url);
    }

    public function getResult() {
        return $this->result;
    }
}

$urls = ['https://api.example.com/1', 'https://api.example.com/2', 'https://api.example.com/3'];
$threads = [];

foreach ($urls as $url) {
    $thread = new UrlFetcher($url);
    $thread->start(); // Запускаем поток
    $threads[] = $thread;
}

// Собираем результаты
foreach ($threads as $thread) {
    $thread->join(); // Ждём завершения
    echo $thread->getResult() . PHP_EOL;
}
?>

Код запускает три потока, каждый качает свой URL. Время выполнения примерно равно времени самого медленного запроса, а не сумме всех трёх. Отличная иллюстрация параллельного выполнения в PHP.

Когда использовать pthreads?

  • Тяжёлые CPU-задачи (обработка изображений, шифрование, рендеринг PDF).
  • Парсинг больших объёмов данных в CLI-скриптах.
  • Когда нужна настоящая параллельность, а не кооперативная многозадачность.

2. Горутины в PHP: Fiber — лёгкость и контроль

Что такое Fiber?

Начиная с PHP 8.1, в языке появились Fiber — это аналог горутин из Go или корутин из Python. В отличие от потоков, Fiber не создаёт отдельный процесс ОС. Это лёгкая единица выполнения, которая работает в одном потоке и переключается по команде разработчика.

Ключевая идея: вы сами решаете, когда приостановить текущий Fiber и передать управление другому. Это называется кооперативная многозадачность. Идеально для I/O-операций (запросы к API, чтение файлов, работа с БД).

Пример: асинхронный обход директорий

<?php
function readFileAsync(string $path): Fiber {
    return new Fiber(function () use ($path) {
        $handle = fopen($path, 'r');
        $content = '';
        while (!feof($handle)) {
            $chunk = fread($handle, 4096);
            $content .= $chunk;
            // Приостанавливаем Fiber, чтобы дать шанс другим
            Fiber::suspend();
        }
        fclose($handle);
        return $content;
    });
}

$files = ['/tmp/a.txt', '/tmp/b.txt', '/tmp/c.txt'];
$fibers = [];

foreach ($files as $file) {
    $fibers[] = readFileAsync($file);
}

// Запускаем все Fibers по очереди
$running = $fibers;
while (!empty($running)) {
    foreach ($running as $key => $fiber) {
        if ($fiber->isStarted()) {
            $fiber->resume();
        } else {
            $fiber->start();
        }
        if ($fiber->isTerminated()) {
            echo $fiber->getReturn() . PHP_EOL;
            unset($running[$key]);
        }
    }
}
?>

Здесь три Fiber читают файлы «одновременно» — переключение происходит после каждого 4KB блока. Никаких блокировок, никаких лишних потоков.

Где пригодятся горутины PHP?

  • Обработка множества HTTP-запросов (как в WebSocket-серверах).
  • Построчное чтение больших логов.
  • Любые сценарии, где много ожидания (I/O bound).

3. Async/await в PHP: элегантная асинхронность

Как это выглядит?

Синтаксис async/await в PHP не встроен в ядро (как в JavaScript), но реализован через библиотеки — например, Amp или ReactPHP. Они предоставляют событийный цикл (event loop) и генераторы, которые эмулируют привычный асинхронный синтаксис.

С PHP 8.1 + Fiber эти библиотеки стали работать ещё эффективнее, потому что они используют Fiber под капотом для «лёгких» переключений.

Пример с Amp (установка: composer require amphp/amp)

<?php
require __DIR__ . '/vendor/autoload.php';

use function Amp\async;
use function Amp\await;

// Асинхронная функция, возвращает Fiber
function fetchData(string $url): string {
    return async(function () use ($url) {
        $response = file_get_contents($url); // В реальности используйте amphp/http-client
        return $response;
    });
}

// Используем await для ожидания результата
$result1 = await(fetchData('https://api.example.com/data1'));
$result2 = await(fetchData('https://api.example.com/data2'));

echo $result1 . $result2;
?>

Обратите внимание: async возвращает Fiber, await приостанавливает выполнение до его завершения. Всё работает в одном потоке, но без блокировки.

Почему async/await — это удобно?