Пакет · thread

Корректное завершение

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

Ожидание с таймаутом

По умолчанию join() блокируется навсегда. Передайте таймаут (в секундах), чтобы ограничить ожидание — он вернёт null, если дедлайн наступит раньше, чем задача завершится:

php
$exitCode = $thread->join(timeout: 30);

if ($exitCode === null) {
  // Всё ещё выполняется спустя 30 с — решаем, что делать дальше
  echo "Timed out; asking it to stop...\n";
} elseif ($exitCode === 0) {
  echo "Finished cleanly.\n";
} else {
  echo "Failed with exit code {$exitCode}.\n";
}

Возвращаемые значения join()

int — код выхода при завершении · null — при таймауте · -1, если thread не запускался. Полная таблица — в справочнике API.

Сначала по-хорошему, потом силой

Паттерн чистого завершения: отправьте SIGTERM (перехватываемый, даёт задаче прибраться), дайте немного времени и переходите к SIGKILL (неперехватываемый) только если вас проигнорировали:

php
$thread->terminate();            // SIGTERM — просьба завершиться

if ($thread->join(timeout: 5) === null) {
  echo "Ignored SIGTERM; killing.\n";
  $thread->kill();             // SIGKILL — нельзя перехватить или проигнорировать
  $thread->join();
}
Метод Сигнал Перехватывается? Когда использовать
interrupt() SIGINT да Прерывание в стиле Ctrl+C
terminate() SIGTERM да Просьба о чистом завершении
kill() SIGKILL нет Крайняя мера после terminate()

Обработайте SIGTERM внутри задачи

Чтобы terminate() был корректным, задача должна слушать сигнал и останавливаться в безопасной точке:

src/BatchJob.php
<?php

use Flytachi\Winter\Thread\Runnable;

class BatchJob implements Runnable
{
  public function run(array $args): void
  {
      pcntl_async_signals(true);

      $stop = false;
      pcntl_signal(SIGTERM, function () use (&$stop) {
          $stop = true; // не делайте тяжёлую работу в обработчике сигнала
      });

      foreach ($this->chunks() as $chunk) {
          if ($stop) {
              $this->checkpoint(); // сохраняем прогресс и выходим чисто
              return;
          }
          $this->process($chunk);
      }
  }
}

Пауза и возобновление

Нужно приостановить процесс, не завершая его? pause() отправляет SIGSTOP (ОС замораживает процесс; он остаётся в таблице процессов, поэтому isAlive() по-прежнему true), а resume() отправляет SIGCONT:

php
$thread->pause();   // заморожен ОС — нельзя перехватить
// ... освобождаем CPU под другое ...
$thread->resume();  // продолжает с того же места

Связанное