Конкурентность и многопоточность в PHP: практическое руководство

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

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

Начать курс

Введение: зачем 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                

Блоги

Рекомендаций по книгам