Введение: зачем PHP нужна конкурентность
PHP традиционно считается синхронным языком, где каждый запрос обрабатывается последовательно. Однако современные веб-приложения требуют выполнения множества задач одновременно: обработка очередей, работа с API, обработка изображений, парсинг данных. Конкурентность и многопоточность в PHP позволяют решить эти задачи, повышая производительность и отзывчивость приложений.
В этой статье разберём основные подходы: многопоточность через pthreads, параллельные процессы через pcntl_fork, асинхронное программирование с помощью ReactPHP и современные Fibers в PHP 8.1+. Каждый метод имеет свои сильные стороны и ограничения, которые мы рассмотрим на практических примерах.
Подход 1: Многопоточность с расширением pthreads
pthreads — это расширение для работы с потоками (threads) в PHP. Оно доступно только для CLI-версии и требует сборки с флагом --enable-maintainer-zts. Потоки разделяют память, что позволяет эффективно обмениваться данными, но требует осторожности с блокировками.
Установка и настройка
# Установка через pecl
pecl install pthreads
# Проверка в php.ini
extension=pthreads.so
Пример: параллельная обработка массива
<?php
class WorkerThread extends Thread {
private $data;
private $result;
public function __construct($data) {
$this->data = $data;
}
public function run() {
// Имитация тяжёлой операции
$this->result = array_map(function($item) {
sleep(1); // 1 секунда на элемент
return $item * 2;
}, $this->data);
}
public function getResult() {
return $this->result;
}
}
// Создаём 5 потоков
$threads = [];
$dataChunks = array_chunk(range(1, 100), 20);
foreach ($dataChunks as $chunk) {
$thread = new WorkerThread($chunk);
$thread->start();
$threads[] = $thread;
}
// Сбор результатов
$finalResult = [];
foreach ($threads as $thread) {
$thread->join();
$finalResult = array_merge($finalResult, $thread->getResult());
}
echo "Обработано " . count($finalResult) . " элементов за ~5 секунд вместо 100.";
Важно: pthreads не работает в веб-окружении (Apache/Nginx + mod_php). Используйте только для CLI-скриптов, воркеров и демонов.
Подход 2: Параллельные процессы через pcntl_fork
Функция pcntl_fork() создаёт дочерний процесс, копируя текущий скрипт. Процессы изолированы по памяти, что безопаснее потоков, но межпроцессное взаимодействие (IPC) требует дополнительных механизмов: очереди сообщений, разделяемая память или сокеты.
Пример: параллельная загрузка URL
<?php
$urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3',
];
$pids = [];
foreach ($urls as $url) {
$pid = pcntl_fork();
if ($pid == -1) {
die("Ошибка fork");
} elseif ($pid == 0) {
// Дочерний процесс
$content = file_get_contents($url);
file_put_contents('/tmp/' . md5($url) . '.json', $content);
exit(0); // Важно завершить дочерний процесс
} else {
$pids[] = $pid;
}
}
// Ожидание завершения всех дочерних процессов
foreach ($pids as $pid) {
pcntl_waitpid($pid, $status);
}
echo "Все URL загружены параллельно.";
Ограничения:
- Не работает с веб-сервером (только CLI).
- Каждый процесс потребляет память родителя.
- Сложно синхронизировать доступ к общим ресурсам.
Подход 3: Асинхронность с ReactPHP
ReactPHP — это библиотека для событийно-ориентированного программирования. Она реализует конкурентность без многопоточности: один процесс обрабатывает множество операций ввода-вывода с помощью цикла событий (event loop). Идеально для сетевых приложений, чатов, прокси-серверов.
Установка через Composer
composer require react/event-loop react/http react/socket
Пример: асинхронный HTTP-клиент
<?php
require 'vendor/autoload.php';
use React\EventLoop\Factory;
use React\Http\Browser;
$loop = Factory::create();
$client = new Browser($loop);
$urls = [
'https://jsonplaceholder.typicode.com/posts/1',
'https://jsonplaceholder.typicode.com/posts/2',
'https://jsonplaceholder.typicode.com/posts/3',
];
$promises = [];
foreach ($urls as $url) {
$promises[] = $client->get($url);
}
// Обработка всех ответов параллельно
React\Promise\all($promises)->then(function ($responses) {
foreach ($responses as $response) {
echo $response->getBody() . "\n---\n";
}
}, function ($error) {
echo "Ошибка: " . $error->getMessage() . "\n";
});
$loop->run();
Преимущества:
- Низкое потребление памяти (один процесс).
- Работает в CLI и через FastCGI.
- Отлично подходит для I/O-bound задач.
Подход 4: Fibers в PHP 8.1+
Начиная с PHP 8.1, в язык добавлены Fibers — легковесные корутины, которые позволяют приостанавливать и возобновлять выполнение функции без блокировки всего потока. Это нативный механизм кооперативной многозадачности.
Пример: асинхронный парсинг с Fibers
<?php
function fetchUrl(string $url): string {
$fiber = Fiber::getCurrent();
// Эмуляция асинхронного HTTP-запроса
Fiber::suspend($url); // Передаём управление обратно
return file_get_contents($url);
}
$fiber1 = new Fiber(function() {
$result = fetchUrl('https://example