Быстрый старт
От установки до работающей фоновой задачи за пять минут: вы напишете задачу, запустите её в отдельном процессе, дождётесь её и убедитесь, что она завершилась.
Что вы соберёте
Задачу ReportGenerator, которая делает запрос к базе данных и пишет файл отчёта. Поскольку она
может быть медленной, вы запустите её в фоне, чтобы основной скрипт не блокировался, — затем
дождётесь её и проверите результат. Весь цикл целиком: описать → запустить → дождаться → проверить.
Перед началом
- PHP ≥ 8.4 на POSIX-совместимой ОС (Linux, macOS, BSD — не Windows)
- Расширения
ext-pcntlиext-posix(проверьте черезphp -m)
Установите пакет:
composer require flytachi/winter-threadВсе подробности — на странице Установка и требования.
Шаг 1 — Напишите задачу
Задача — это любой класс, реализующий Runnable. Вся работа выполняется внутри run().
<?php
use Flytachi\Winter\Thread\Runnable;
class ReportGenerator implements Runnable
{
public function __construct(private int $reportId) {}
public function run(array $args): void
{
// Создавайте ресурсы ВНУТРИ run(), никогда в конструкторе.
$db = new PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$orders = $db
->query("SELECT * FROM orders WHERE report_id = {$this->reportId}")
->fetchAll();
file_put_contents(
"/tmp/report-{$this->reportId}.json",
json_encode($orders)
);
}
}Почему ресурсы создаются внутри run()
Объект задачи сериализуется и передаётся в отдельный процесс, поэтому его свойства не могут хранить
живые ресурсы — соединения с БД, файловые дескрипторы или сокеты. Оставьте в конструкторе только
простые данные (id, пути), а соединения открывайте внутри run(). Подробнее — в
справочнике по Runnable.
Шаг 2 — Запустите в фоне
Оберните задачу в Thread и вызовите start(). Он сразу возвращает PID дочернего процесса —
ваш скрипт никогда не блокируется.
<?php
require 'vendor/autoload.php';
use Flytachi\Winter\Thread\Thread;
$thread = new Thread(
new ReportGenerator(42),
'Billing', // пространство имён — видно в `ps`
'ReportGenerator', // имя
'report-42' // тег
);
$pid = $thread->start();
echo "Report started in the background (PID: {$pid})\n";
// Ваш скрипт продолжает работать, пока отчёт генерируется в отдельном процессе.
echo "Main script is free to do other work...\n";Куда девается вывод?
По умолчанию вывод идёт в /dev/null — безопасно для задач по принципу «запустил и забыл», pipe не
открывается. Чтобы захватывать логи или читать вывод в реальном времени, см.
Отладку и вывод.
Посмотрите на процесс
Пока задача выполняется, ps показывает процесс под его заголовком:
WinterThread Billing -> ReportGenerator@report-42.
Шаг 3 — Дождитесь и проверьте результат
Нужен результат перед тем, как продолжить? Вызовите join(). Он блокируется, пока дочерний процесс
не завершится, и возвращает код выхода (0 = успех).
$exitCode = $thread->join();
if ($exitCode === 0) {
echo "Done. Report written to /tmp/report-42.json\n";
} else {
echo "Report failed (exit code: {$exitCode}).\n";
}Проверьте файл, который создала задача:
cat /tmp/report-42.jsonЕсли вы видите JSON — ваша первая фоновая задача отработала от начала до конца. 🎉
Всё целиком
Полная версия для копирования:
<?php
require 'vendor/autoload.php';
use Flytachi\Winter\Thread\Runnable;
use Flytachi\Winter\Thread\Thread;
class ReportGenerator implements Runnable
{
public function __construct(private int $reportId) {}
public function run(array $args): void
{
$db = new PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$orders = $db
->query("SELECT * FROM orders WHERE report_id = {$this->reportId}")
->fetchAll();
file_put_contents(
"/tmp/report-{$this->reportId}.json",
json_encode($orders)
);
}
}
$thread = new Thread(new ReportGenerator(42), 'Billing', 'ReportGenerator', 'report-42');
$pid = $thread->start();
echo "Report started in the background (PID: {$pid})\n";
$exitCode = $thread->join();
echo $exitCode === 0
? "Done. Report written to /tmp/report-42.json\n"
: "Report failed (exit code: {$exitCode}).\n";Что только что произошло
- Отдельный процесс ОС выполнил вашу задачу в полной изоляции — его падение не может уронить основной скрипт.
- Задача пересекла границу процессов через сериализацию — поэтому ресурсы создаются внутри
run()(Шаг 1). start()вернулся мгновенно с PID; вывод по умолчанию ушёл в/dev/null.join()заблокировался до завершения дочернего процесса и вернул его код выхода.
Дальше
- Установка и требования — расширения и настройка bootstrap
- Ментальная модель — как это работает и почему
- Обработчик очереди — запуск задач и передача аргументов
- Корректное завершение — таймауты и остановка работы
- Справочник API — каждый метод и сигнал
- Режимы payload — работа под Swoole и event-loop