Как я могу надежно очистить потоки 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, поскольку он обеспечивает цикл обработки событий. Вы можете либо адаптировать все ваше приложение к циклу событий, и вам не нужно создавать потоки, или вы можете использовать цикл событий для каждого потока, который может заблокировать и позволить ему прослушивать дополнительный канал, чтобы вы могли его разбудить, что он может сам себя отключить. Первый может оказаться невозможным, в зависимости от того, сколько кода у вас уже есть, а второй звучит как перебор.

Другие вопросы по тегам