spawn_thread
(PHP 8.6+, True Async 1.0)
spawn_thread() — запускает замыкание в отдельном параллельном потоке с собственным изолированным окружением PHP. Возвращает Async\Thread, реализующий Completable, поэтому поток можно ожидать через await().
Описание
Async\spawn_thread(
\Closure $task,
bool $inherit = true,
?\Closure $bootloader = null
): Async\ThreadСоздаёт новый OS-поток, поднимает в нём отдельный PHP-request, при необходимости выполняет $bootloader, затем исполняет $task. Возвращаемое из $task значение становится результатом потока и доступно через await() или Thread::getResult().
Параметры
task : Замыкание, выполняемое в потоке-получателе. Может захватывать переменные через use (...) — они глубоко копируются через общую память при создании потока и заново оживают уже в памяти потока-получателя.
inherit : Зарезервировано для будущего использования. На данный момент параметр принимается, но не влияет на поведение потока — поток-получатель всегда стартует со свежим изолированным окружением. Флаг останется в сигнатуре до появления поддержки наследования классов и функций из родителя.
bootloader : Необязательное замыкание, выполняемое первым в потоке-получателе, до загрузки переменных из use(...) основного $task. Используется для подготовки окружения потока: регистрация autoload, объявление классов, инициализация ini, подключение библиотек. Bootloader не принимает параметров, его возвращаемое значение игнорируется.
Возвращаемое значение
Объект Async\Thread, представляющий запущенный поток. Реализует Completable, поэтому пригоден для await(), await_all(), await_any(), Task\Group и т.п.
Исключения
Async\ThreadTransferException— выбрасывается в родителе, если одна из переменных, захваченных черезuse(...), содержит непередаваемый тип (stdClassс динамическими свойствами, PHP-ссылку, ресурс и т.п.).Async\RemoteException— выбрасывается приawait(), если$taskзавершился ошибкой. Оборачивает исходное исключение;getRemoteClass()иgetRemoteException()дают доступ к деталям.
Примеры
Пример #1. Тяжёлая работа в отдельном потоке
<?php
use function Async\spawn;
use function Async\spawn_thread;
use function Async\await;
use function Async\delay;
// Тикер в основной корутине — показывает, что основная программа не встаёт
spawn(function() {
for ($i = 0; $i < 5; $i++) {
echo "tick $i\n";
delay(100);
}
});
spawn(function() {
$thread = spawn_thread(function() {
$sum = 0;
for ($i = 0; $i < 5_000_000; $i++) {
$sum += sqrt($i);
}
return $sum;
});
$result = await($thread);
echo "heavy done: ", (int) $result, "\n";
});tick 0
tick 1
tick 2
tick 3
tick 4
heavy done: 7453558806Тикер отрабатывает параллельно с CPU-нагрузкой — основная программа не застревает.
Пример #2. Передача переменных и идентичность
<?php
use function Async\spawn;
use function Async\spawn_thread;
use function Async\await;
class Config {
public function __construct(public string $name = '') {}
}
// Класс не наследуется в поток — объявляем его через bootloader
$boot = function() {
eval('class Config { public function __construct(public string $name = "") {} }');
};
spawn(function() use ($boot) {
$obj = new Config('prod');
$meta = ['ref' => $obj];
$thread = spawn_thread(
task: function() use ($obj, $meta) {
// Одна и та же инстанция в двух переменных из use(...)
echo "same: ", ($obj === $meta['ref'] ? "yes" : "no"), "\n";
// Мутация через одну ссылку видна через другую
$obj->name = 'staging';
echo "meta: ", $meta['ref']->name, "\n";
return $obj->name;
},
bootloader: $boot,
);
echo "result: ", await($thread), "\n";
});same: yes
meta: staging
result: stagingИдентичность объекта сохраняется через разные переменные, захваченные одним замыканием через use(...).
Пример #3. Обработка исключения
<?php
use function Async\spawn;
use function Async\spawn_thread;
use function Async\await;
spawn(function() {
$thread = spawn_thread(function() {
throw new RuntimeException('boom');
});
try {
await($thread);
} catch (Async\RemoteException $e) {
echo "remote class: ", $e->getRemoteClass(), "\n";
$original = $e->getRemoteException();
if ($original !== null) {
echo "original: ", $original->getMessage(), "\n";
}
}
});remote class: RuntimeException
original: boomПример #4. Передача непередаваемого типа
<?php
use function Async\spawn;
use function Async\spawn_thread;
use function Async\await;
spawn(function() {
$obj = new stdClass(); // dynamic properties
$obj->x = 1;
try {
$thread = spawn_thread(function() use ($obj) {
return 'unreachable';
});
await($thread);
} catch (Async\ThreadTransferException $e) {
echo $e->getMessage(), "\n";
}
});Cannot transfer object with dynamic properties between threads (class stdClass). Use arrays insteadИсключение брошено в родителе на этапе копирования переменных из use(...) — поток-получатель даже не успел запуститься.
Пример #5. Возврат результата через FutureState
Если нужно «разбудить» родительский Future прямо из параллельного потока (например, чтобы одно и то же событие можно было ждать из разных мест основной корутины) — передайте FutureState:
<?php
use Async\FutureState;
use Async\Future;
use function Async\spawn;
use function Async\spawn_thread;
use function Async\await;
spawn(function() {
$state = new FutureState();
$future = new Future($state);
$thread = spawn_thread(function() use ($state) {
$data = "computed in thread";
$state->complete($data);
});
// Событие придёт в родителя через $future, когда поток вызовет $state->complete()
$result = await($future);
echo "got: ", $result, "\n";
await($thread);
echo "thread done\n";
});got: computed in thread
thread doneFutureState можно передать в spawn_thread только один раз — попытка передать тот же state во второй поток бросит исключение на этапе транзита.
Примечания
- Класс замыкания —
$taskдолжен быть\Closure. Callables других типов ([object, 'method'], строковое имя функции) не принимаются — механизм передачи умеет переносить толькоClosure. useс&(by-reference) — отклоняется. Shared reference между потоками не имеет смысла.- Пользовательские классы не наследуются в поток-получатель автоматически. Если
$taskиспользует класс, объявленный в родительском скрипте, его нужно сделать доступным в потоке черезbootloader(подключить через autoload или объявить черезeval). - Статические свойства функций и классов в потоке-получателе свои — любые изменения остаются внутри потока и не утекают наружу.
См. также
Async\Thread— документация по компонентуAsync\ThreadChannel— каналы между потокамиawait()— ожидание результатаspawn()— запуск корутины (не потока)