Как проверить, закончил ли поток в Rust?
Когда я spawn
нить в Rust я получаю JoinHandle
, что хорошо для... присоединения (блокирующая операция), и не более того. Как я могу проверить, вышел ли дочерний поток (т.е. JoinHandle.join()
не будет блокировать) из родительского потока? Бонусные баллы, если вы знаете, как убить дочернюю нить.
Я полагаю, что вы могли бы сделать это, создав канал, отправив что-нибудь ребенку и обнаружив ошибки, но это кажется ненужной сложностью и накладными расходами.
5 ответов
Начиная с Rust 1.7, в стандартной библиотеке нет API для проверки, вышел ли дочерний поток без блокировки.
Портативный обходной путь - использовать каналы для отправки сообщения от ребенка родителю, чтобы сообщить, что ребенок собирается выйти. Receiver
имеет неблокирующую try_recv
метод. когда try_recv
получает сообщение, вы можете использовать join()
на JoinHandle
чтобы получить результат потока.
Существуют также нестабильные характерные для платформы характеристики расширения, которые позволяют вам получить необработанный дескриптор потока. Затем вам придется написать специфичный для платформы код, чтобы проверить, завершился ли поток или нет.
Если вы считаете, что эта функция должна быть в стандартной библиотеке Rust, вы можете отправить RFC (обязательно сначала прочтите README!).
Бонусные баллы, если вы знаете, как убить дочернюю нить.
Потоки в Rust реализованы с использованием собственных потоков ОС. Несмотря на то, что операционная система может предоставить способ уничтожения потока, это плохая идея, поскольку ресурсы, выделенные потоком, не будут очищены до завершения процесса.
Это возможно, друзья. Используйте refcounters, которые Rust упадет подряд или паникует. 100% безопасно. Пример:
use std::time::Duration;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
fn main() {
// Play with this flag
let fatal_flag = true;
let do_stop = true;
let working = Arc::new(AtomicBool::new(true));
let control = Arc::downgrade(&working);
thread::spawn(move || {
while (*working).load(Ordering::Relaxed) {
if fatal_flag {
panic!("Oh, my God!");
} else {
thread::sleep(Duration::from_millis(20));
println!("I'm alive!");
}
}
});
thread::sleep(Duration::from_millis(50));
// To stop thread
if do_stop {
match control.upgrade() {
Some(working) => (*working).store(false, Ordering::Relaxed),
None => println!("Sorry, but thread died already."),
}
}
thread::sleep(Duration::from_millis(50));
// To check it's alive / died
match control.upgrade() {
Some(_) => println!("Thread alive!"),
None => println!("Thread ends!"),
}
}
Суть: https://gist.github.com/DenisKolodin/edea80f2f5becb86f718c330219178e2
На игровой площадке: https://play.rust-lang.org/?gist=9a0cf161ba0bbffe3824b9db4308e1fb&version=stable&backtrace=0
UPD: я создал thread-control
ящик, который реализует этот подход: https://github.com/DenisKolodin/thread-control
Краткий ответ пока невозможен. Но это не тот вопрос, который действительно нужно решать.
Бонусные баллы, если вы знаете, как убить дочернюю нить.
НИКОГДА
Даже в тех языках, которые поддерживают уничтожение потоков ( см. Здесь Java), рекомендуется этого не делать.
Выполнение потока обычно кодируется с явными точками взаимодействия, и часто существуют неявные предположения, что никакого другого прерывания не произойдет.
Самым вопиющим примером являются, конечно, ресурсы: наивный метод "kill" должен был бы остановить выполнение потока; это будет означать не выпускать какой-либо ресурс. Вы можете думать о памяти, это меньше всего беспокоит вас. Вообразите вместо этого все Mutex
которые не разблокированы и создадут тупики позже...
Другим вариантом будет ввести panic
в потоке, что приведет к размотке. Тем не менее, вы не можете просто начать раскручивать в любой момент! Программа должна определить безопасные точки, в которых panic
будет гарантированно безопасным (внедрение его в любой другой точке означает потенциальное повреждение общих объектов); как определить такие безопасные точки и ввести panic
существует проблема открытых исследований на родных языках, особенно тех, которые выполняются в системах W^X
(где страницы памяти либо записываемые, либо исполняемые, но никогда не оба).
Таким образом, не существует известного способа безопасного (как по памяти, так и по функциональности) уничтожения потока.
Начиная с rust 1.61.0, есть
is_finished
метод.
https://doc.rust-lang.org/stable/std/thread/struct.JoinHandle.html#method.is_finished
Я думаю, что Arc можно использовать для решения этой проблемы. Если поток завершается, счетчик ссылок уменьшается на единицу.