Пакет · thread

Модель работы

Одна мысль объясняет почти каждое правило этой библиотеки: «thread» здесь — это отдельный процесс операционной системы, а не поток в памяти. Держите её в голове — остальное вытекает само.

Не тот thread, что вы могли ожидать

Во многих языках поток разделяет память с кодом, который его запустил. Winter Thread так не делает. Каждый запущенный Thread — это отдельный процесс PHP, порождённый ОС, со своей памятью, своими файловыми дескрипторами, своим всем.

Представляйте это не как второго сотрудника за вашим столом, который смотрит в ваши записи, а как запечатанный конверт, отправленный коллеге в другом здании: он вскрывает его, делает работу и присылает результат обратно. Вашего стола он не касается.

Это осознанный компромисс:

  • Изоляция — фатальная ошибка, утечка памяти или segfault в дочернем процессе не могут повредить или уронить ваш основной скрипт или его «соседей».
  • Настоящий параллелизм — ОС может распределить каждый процесс на отдельное ядро CPU.
  • Взамен — нет общей памяти, поэтому нельзя просто прочитать переменную, которую изменил дочерний процесс.

Граница сериализации

Вот часть, которую стоит усвоить. Когда вы запускаете задачу, объект Runnable сериализуется (замораживается в строку), передаётся в новый процесс и десериализуется (размораживается) на другой стороне. Только после этого выполняется run().

text
Родительский процесс                Дочерний процесс
--------------------                ----------------
new ReportGenerator(42)
      │  сериализация

"...замороженные байты..."  ────►  десериализация → ReportGenerator(42)


                                       run()

Отсюда следуют два вывода, которые объясняют правила, встречающиеся повсюду:

  1. Никаких живых ресурсов в свойствах. Соединение с БД, открытый файл или сокет нельзя заморозить и передать. Оставьте в конструкторе только простые данные (id, пути, флаги), а соединения открывайте внутри run().
  2. Изменения в дочернем процессе остаются в нём. Всё, что run() меняет в $this — или в памяти — исчезает при завершении процесса. Чтобы вернуть результат, пишите в файл или базу данных либо сигнализируйте об успехе через код выхода.

Вот почему

Каждое замечание «создавайте ресурсы внутри run()» и «свойства должны быть сериализуемыми» в этой документации восходит к этой единственной границе. Это не причуда — это цена изоляции.

Что происходит при вызове start()

Спокойно, от начала до конца:

  1. Ваш родительский скрипт сериализует Runnable.
  2. Он запускает новый процесс PHP с внутренним runner-скриптом.
  3. start() сразу возвращает PID дочернего процесса — ваш скрипт не блокируется.
  4. Runner десериализует задачу и вызывает run().
  5. Когда вы вызываете join(), родитель ждёт завершения этого процесса и читает его код выхода.

Это весь жизненный цикл. Детальная версия — дескрипторы, внутренности runner, как устроен вывод — в Глубокое погружение → Жизненный цикл runner.

Когда стоит применять

Отлично подходит независимая, долгая или блокирующая работаНе подходит параллелизм с общей памятью или тонкий IPC
  • ✅ Вынос медленных задач (кодирование, отчёты, массовые рассылки), чтобы запрос оставался отзывчивым.
  • ✅ Одновременный запуск нескольких независимых задач на разных ядрах.
  • ❌ Задачи, которым нужно постоянно разделять и менять одно и то же состояние в памяти — процессы не делят память; координируйтесь через файлы, базу данных или очередь.

Дальше