Пакет · thread

Быстрый старт

От установки до работающей фоновой задачи за пять минут: вы напишете задачу, запустите её в отдельном процессе, дождётесь её и убедитесь, что она завершилась.

Время ~5 минУровень НачальныйТребования PHP 8.4+

Что вы соберёте

Задачу ReportGenerator, которая делает запрос к базе данных и пишет файл отчёта. Поскольку она может быть медленной, вы запустите её в фоне, чтобы основной скрипт не блокировался, — затем дождётесь её и проверите результат. Весь цикл целиком: описать → запустить → дождаться → проверить.

Перед началом

  • PHP ≥ 8.4 на POSIX-совместимой ОС (Linux, macOS, BSD — не Windows)
  • Расширения ext-pcntl и ext-posix (проверьте через php -m)

Установите пакет:

bash
composer require flytachi/winter-thread

Все подробности — на странице Установка и требования.

Шаг 1 — Напишите задачу

Задача — это любой класс, реализующий Runnable. Вся работа выполняется внутри run().

src/ReportGenerator.php
<?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 дочернего процесса — ваш скрипт никогда не блокируется.

run-report.php
<?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 = успех).

php
$exitCode = $thread->join();

if ($exitCode === 0) {
  echo "Done. Report written to /tmp/report-42.json\n";
} else {
  echo "Report failed (exit code: {$exitCode}).\n";
}

Проверьте файл, который создала задача:

bash
cat /tmp/report-42.json

Если вы видите JSON — ваша первая фоновая задача отработала от начала до конца. 🎉

Всё целиком

Полная версия для копирования:

run-report.php
<?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() заблокировался до завершения дочернего процесса и вернул его код выхода.

Дальше