Как я могу надежно очистить потоки Rust, выполняющие блокировку ввода-вывода?
Похоже, в Rust часто встречается идиот, чтобы создать поток для блокировки ввода-вывода, чтобы вы могли использовать неблокирующие каналы:
use std::sync::mpsc::channel;
use std::thread;
use std::net::TcpListener;
fn main() {
let (accept_tx, accept_rx) = channel();
let listener_thread = thread::spawn(move || {
let listener = TcpListener::bind(":::0").unwrap();
for client in listener.incoming() {
if let Err(_) = accept_tx.send(client.unwrap()) {
break;
}
}
});
}
Проблема в том, что воссоединение потоков, как это, зависит от порожденного потока, "понимающего", что принимающая сторона канала была отброшена (то есть, вызов send(..)
возвращается Err(_)
):
drop(accept_rx);
listener_thread.join(); // blocks until listener thread reaches accept_tx.send(..)
Вы можете сделать фиктивные соединения для TcpListener
и выключение TcpStream
с помощью клона, но это похоже на действительно хакерские способы очистки таких потоков, и в настоящее время я даже не знаю, как взломать, чтобы вызвать блокировку потока при чтении из stdin
присоединиться.
Как я могу очистить потоки, подобные этим, или моя архитектура просто неправильно?
2 ответа
Просто невозможно безопасно отменить поток в Windows или Linux/Unix/POSIX, поэтому он не доступен в стандартной библиотеке Rust.
Вот внутренняя дискуссия об этом.
Есть много неизвестных, которые приходят от принудительной отмены тем. Это может стать действительно грязным. Помимо этого, сочетание потоков и блокирующего ввода-вывода всегда будет сталкиваться с этой проблемой: вам нужно, чтобы каждый блокирующий вызов ввода-вывода имел тайм-ауты, чтобы он даже мог быть надежно прерываемым. Если вы не можете написать асинхронный код, нужно либо использовать процессы (которые имеют определенную границу и могут быть принудительно завершены операционной системой, но, очевидно, сопряжены с более тяжелым весом и проблемами обмена данными), либо неблокирующий ввод-вывод, который будет верните ваш поток обратно в цикл обработки событий, который прерван.
MIO доступен для асинхронного кода. Tokio - это контейнер более высокого уровня, основанный на mio, что делает написание неблокирующего асинхронного кода еще более простым.
tldr; Фиктивное соединение может быть самым простым способом.
(Я предполагаю, что Linux как ОС.)
listener.incoming () вызовет.accept() для TcpListener в своем методе.next(), и поток застрянет в вызове accept для os. Насколько я знаю, он может быть возвращен только по желанию с помощью попытки подключения или сигнала, или если сокет установлен неблокирующим.
обработка сигналов не поддерживается стандартными библиотеками ржавчины.
Дескриптор файла сокета, по-видимому, недоступен в TcpListener, поэтому вы не можете установить его в неблокирующий режим. Также это подразумевает опрос, это может быть плохой идеей.
Альтернативой может быть использование mio, поскольку он обеспечивает цикл обработки событий. Вы можете либо адаптировать все ваше приложение к циклу событий, и вам не нужно создавать потоки, или вы можете использовать цикл событий для каждого потока, который может заблокировать и позволить ему прослушивать дополнительный канал, чтобы вы могли его разбудить, что он может сам себя отключить. Первый может оказаться невозможным, в зависимости от того, сколько кода у вас уже есть, а второй звучит как перебор.