У многих исторически сложилось мнение, что PHP никогда не был и не будет многопоточным. Однако это глубокое заблуждение, из-за которого нередко можно наблюдать, как разработчики отдают предпочтение Java и другим языкам программирования.
В этой статье будут:
• рассмотрены условия и область применения многопоточного PHP;
• перечислены зависимости, необходимые для поддержки многопоточности;
• процедуры, необходимые для работы OctoberCMS с многопоточностью;
• рассмотрена практика использования с OctoberCMS (и примеры кода);
• мои личные рекомендации.
Условия и область применения
Многопоточный PHP можно использовать исключительно в консольной среде (CLI). На уровне HTTP (FPM), к сожалению (или к счастью), создание пулов потоков не представляется возможным.
В OctoberCMS многопоточность можно применить при создании консольных команд и обработчиков очередей.
Зависимости
Многопоточность в PHP обеспечивается расширением PCNTL, который по умолчанию включен в пакет CLI.
Проверить, установлено ли расширение PCNTL на сервере можно командой:
php -i | grep pcntl
Вывод будет содержать следующую информацию, означающую, что расширение активно:
pcntl
pcntl support => enabled
Если расширение не было найдено, установить его можно простой командой:
apt-get install php-cli
А также установим библиотеку spatie/async для работы с потоками, запустив команду в корне проекта:
composer require spatie/async
Дружим OctoberCMS со spatie/async
Важно понимать, что поток — это список, состоящий из N независимых друг от друга PHP процессов. Причем эти процессы чистые и не знают о существовании OctoberCMS. Поэтому каждый процесс должен инициализировать фреймворк самостоятельно, чтобы использовать все его возможности.
Создадим в корне проекта файл init.php
:
<?php
/**
* October - The PHP platform that gets back to basics.
*
* @package october/october
* @author Alexey Bobkov, Samuel Georges
*/
/*
|--------------------------------------------------------------------------
| Register composer
|--------------------------------------------------------------------------
|
| Composer provides a generated class loader for the application.
|
*/
require __DIR__.'/bootstrap/autoload.php';
/*
|--------------------------------------------------------------------------
| Load framework
|--------------------------------------------------------------------------
|
| This bootstraps the framework and loads up this application.
|
*/
$app = require_once __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Process request
|--------------------------------------------------------------------------
|
| Execute the request and send the response back to the client.
|
*/
$kernel = $app->make(\Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
Этот скрипт аналогичен index.php за исключением двух строк:
$response->send();
$kernel->terminate($request, $response);
Поэтому вы можете самостоятельно сгенерировать этот файл на основе index’ного.
На этом подготовка завершена. Переходим к практике!
Практика и сценарий использования многопоточности в OctoberCMS
Для демонстрации многопоточности я рассмотрел банальный кейс, в котором необходимо отправить всем зарегистрированным пользователям письмо на электронную почту.
Создадим консольную команду и реализуем многопоточную отправку писем всем пользователям:
<?php namespace DontFollow\Mail\Console;
use Mail;
use Spatie\Async\Pool;
use RainLab\User\Models\User;
use Illuminate\Console\Command;
class SendMail extends Command
{
/**
* @var string The console command name.
*/
protected $name = 'mail:send';
/**
* @var string The console command description.
*/
protected $description = 'Send email to registered users';
/**
* @var integer The maximum PHP pool size.
*/
private $poolMaxSize = 25;
/**
* Execute the console command.
* @return void
*/
public function handle()
{
// Путь до созданного скрипта инициализации фреймворка.
$autoload = base_path('init.php');
User::select('email')->chunk($this->poolMaxSize, function ($users) use ($autoload) {
$pool = Pool::create()
// Явно указываем максимальное количество потоков.
// Если не передать параметр, по умолчанию будет запущено только 15.
->concurrency($users->count())
// Сообщаем путь до скрипта инициализации фреймворка.
// Скрипт будет запущен перед выполнением основной логики.
->autoload($autoload)
->timeout(15);
foreach ($users as $user) {
$pool[] = async(function () use ($user) {
Mail::rawTo($user->email, 'Hello World!');
});
}
await($pool);
});
}
}
Скрипт будет отправлять письма одновременно 25 пользователям за 1-2 секунды, нежели последовательно за 50 секунд, если не дольше.
Рекомендации
Я часто использую библиотеку spatie/async для выполнения тяжелых задач параллельно и готов поделиться рекомендациями при работе с потоками.
• Не используйте многопоток для решения простых задач. Пакет полезен только в том случае, если вы имеете дело с несколькими задачами, обработка каждой из которых занимает не менее нескольких секунд;
• Не перегибайте с количеством одновременных процессов. Оптимальным значением для среднего железа будет 15-20 шт. Для более мощных серверов можно поднять до 30. Опирайтесь на трудоемкость задачи и экспериментируйте. Контролировать процессы достаточно сложно.
Выводы
• PHP многопоточен, но только в режиме CLI;
• Для поддержки многопотока требуется пакет php-cli с расширением PCNTL. А для внедрения в OctoberCMS необходима библиотека spatie/async и инициализирующий фреймворк скрипт. Параллельные задачи выполняются в чистых PHP-процессах. Нет понятия фреймворка, если вы не загрузите его вручную внутри этой задачи.
• В OctoberCMS многопоточность можно использовать при создании консольных команд и обработчиков очередей.