Введение: почему 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 — это удобно?
- Код выглядит как синхронный — легко читать и отлаживать.