读写长时间运行的 std::process::Child

6

我有一个长时间运行的子进程,需要读写大量数据。我有一个读取线程和一个写入线程来分别处理 child.stdoutchild.stdin

extern crate scoped_threadpool;

fn main() {
    // run the subprocess
    let mut child = std::process::Command::new("cat")
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .spawn()
        .unwrap();

    let child_stdout = child.stdout.as_mut().unwrap();
    let child_stdin = std::sync::Mutex::new(child.stdin.as_mut().unwrap());

    let mut pool = scoped_threadpool::Pool::new(2);
    pool.scoped(|scope| {
        // read all output from the subprocess
        scope.execute(move || {
            use std::io::BufRead;
            let reader = std::io::BufReader::new(child_stdout);
            for line in reader.lines() {
                println!("{}", line.unwrap());
            }
        });

        // write to the subprocess
        scope.execute(move || {
            for a in 0..1000 {
                use std::io::Write;
                writeln!(&mut child_stdin.lock().unwrap(), "{}", a).unwrap();
            } // close child_stdin???
        });
    });
}

当写入器完成后,我想关闭child_stdin,以便子进程完成并退出,这样读取器就会看到EOF并且pool.scoped将返回。没有child.wait(),我无法做到这一点,但我也不能调用child.wait(),因为它被两个线程借用。
我该如何使这个程序完整?

如果您正在向子进程编写固定字符串,您可能需要考虑使用subprocess crate,该crate实现了一个communicate方法,用于您现在使用线程的操作。它还公开了一种构建器样式的API,可以将上述表达为let input = (0..1000).map(|i| format!("{}", i)).collect::<String>(); let output = Exec::cmd("cat").stdin(input).stdout(Redirection::Pipe).capt‌ure()?.stdout_str(); 免责声明:我是subprocess的作者。 - user4815162342
1个回答

4

有趣的是,你通过使用 Mutex 共享所有权导致了这个问题 ^_^。不要获取对 child.stdin 的引用,而是完全拥有它并将其传递给线程。当线程完成时,它将被丢弃,隐式地关闭:

let mut child_stdin = child.stdin.unwrap();

// ...

scope.execute(move ||
    for a in 0..1000 {
        use std::io::Write;
        writeln!(&mut child_stdin, "{}", a).unwrap();
    }
    // child_stdin has been moved into this closure and is now
    // dropped, closing it.
);

如果你仍然想要调用wait来获取ExitStatus,那么请将对stdout的引用和对stdin所有权的转移更改为使用Option::take。这意味着child根本没有被借用:

let mut child = // ...

let child_stdout = child.stdout.as_mut().unwrap();
let mut child_stdin = child.stdin.take().unwrap();

// ...

child.wait().unwrap();

是的,确实有效。不幸的是,现在我无法调用child.wait(),但对于我的特定程序来说,我并不需要它。 - njaard
2
@njaard,你想在哪里调用child.wait() - Shepmaster
1
假设情况下,我想在子进程退出后(在“作用域”之外)获取其“ExitStatus”。 - njaard
{btsdaf} - njaard
{btsdaf} - njaard
我还想指出,对于仍然存在问题的人,您还应确保在等待子进程之前删除子进程的标准输出或将其管道传输到 null。我刚刚浪费了很多时间在这上面。 - CoolOppo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接