如何可靠地清理执行阻塞IO的Rust线程?

时间:2015-06-18 04:36:16

标签: multithreading rust blocking channel

在Rust中生成阻塞IO的线程似乎是一个常见的习惯用法,因此您可以使用非阻塞通道:

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 s,但这些似乎是非常简单的方法来清理这些线程,而且就目前而言,我不会&#39甚至知道一个 hack 来触发一个线程阻塞来自stdin的读取加入。

如何清理这些线程,或者我的架构错了?

2 个答案:

答案 0 :(得分:2)

在Windows或Linux / Unix / POSIX中无法安全地取消线程,因此在Rust标准库中无法使用它。

Here is an internals discussion about it

强行取消线程有很多未知因素。它可能变得非常混乱。除此之外,线程和阻塞I / O的组合将始终面临这个问题:您需要每个阻塞I / O调用都有超时,甚至有可能可靠地中断。如果不能编写异步代码,则需要使用进程(具有已定义的边界并且可以强制结束操作系统,但显然会带来更重的权重和数据共享挑战)或非阻塞I / O这将使你的线程回到可中断的事件循环中。

mio可用于异步代码。 Tokio是基于mio的更高级别的包,这使得编写非阻塞异步代码更加直接。

答案 1 :(得分:-1)

tldr;虚拟连接可能是最简单的方法。

(我假设linux为os。)

listener.incoming()将在其.next()方法中调用TcpListener上的.accept() 并且线程将被卡在对os的accept调用中。据我所知,只能通过连接尝试或信号或者套接字被设置为非阻塞而自愿带回。

生锈标准库似乎不支持信号处理。

套接字的文件描述符似乎在TcpListener中不可访问,因此您无法将其设置为非阻塞模式。还有那个 这意味着投票,这可能是一个坏主意。

另一种方法可能是使用mio,因为它提供了一个事件循环。您可以根据事件循环定制整个应用程序,但不需要 做线程,或者你可以为每个线程使用一个可能阻塞的事件循环 并让它听一个额外的管道,所以你可以把它唤醒,它可以 闭嘴自己。第一个可能不再可行,取决于如何 你已经拥有许多代码而第二个代码听起来有点过分。