并发,是指在宏观意义上同一时间处理多个任务。并发的方式一般包含为三种:多进程、多线程以及最近几年刚刚火起来的协程。
首先,我们创建两个项目,一个为子进程,一个为主进程。在子进程的main.rs中,编写如下代码:
use std::thread::sleep;
use std::time::Duration;
fn main() {
println!("Hello, world!");
sleep(Duration::from_secs(5)); // 休息5秒钟
println!("Bye, world!");
}
程序先输出"Hello, world!",然后休息5秒钟,最后打印"Bye, world!"退出。
在主进程的main.rs中,编写如下代码:
use std::process::Command;
fn main() {
Command::new("../sub/target/debug/sub.exe").spawn().unwrap();
}
执行主进程时,发现子进程输出了"Hello, world!",但是没有下文了。有经验的我们都知道,这是因为主进程启动了子进程后立刻退出了。我们需要等待子进程结束。
要等待子进程结束,需要使用一个变量保存子进程对象,然后调用子进程的wait方法:
use std::process::Command;
fn main() {
let mut p = Command::new("../sub/target/debug/sub.exe").spawn().unwrap();
p.wait().unwrap();
}
需要注意的是,因为wait方法会改变子进程对象的状态,所以子进程对象必须是可变的。
与wait对应的,还有一个try_wait方法,try_wait不会阻塞主进程,无论子进程是否结束,都会返回Result,并将状态保存到Ok中:
use std::thread::sleep;
use std::time::Duration;
use std::process::Command;
fn main() {
let mut p = Command::new("../sub/target/debug/sub.exe").spawn().unwrap();
loop {
let status = p.try_wait().unwrap();
match status {
Some(status) => {
if status.success() {
println!("sub process over");
} else {
println!("sub process error");
}
break;
}
None => {
sleep(Duration::from_secs(1));
continue;
}
}
}
}
Ok中保存是一个Option,当子进程结束时,为Some,没有进程时为None。
修改一下子进程,使子进程可以通过命令行参数来决定sleep的时间。接收命令行参数可以使用标准库的env模块:
use std::env::args;
use std::thread::sleep;
use std::time::Duration;
fn main() {
println!("Hello, world!");
let mut args = args();
let secs = args.nth(1).unwrap().parse::().unwrap();
sleep(Duration::from_secs(secs));
println!("Bye, world!");
}
args返回一个比较复杂的结构,可以用nth来取得第几个参数的值,第0个是exe的名称,第1个是传给exe的第1个参数。因为nth有可能是None,所以返回的是Option,可以用unwrap()取得参数的字符串。
Duration::from_secs接收的是u64类型,需要把字符串使用parse::()转成u64类型,转换过程可能会失败,所以parse返回Result,同样可以使用unwrap()取得转换后的值。
接下来,该修改主进程了。主进程的修改比较简单,只需要在Command::new之后添加.arg即可:
use std::process::Command;
fn main() {
let mut p = Command::new("../sub/target/debug/sub.exe").arg("5").spawn().unwrap();
p.wait().unwrap();
}
如果是多个参数,可以使用args函数,将参数合并成一个数组传入就可以了。
Rust当然也可以通过管道进行进程间通信。修改子进程代码,将函数的参数设置为接收数据的条数,每接收到一条数据就原样返回,直到达到设定的条数后退出:
use std::io;
use std::env::args;
fn main() {
println!("Hello, world!");
let mut args = args();
let count = args.nth(1).unwrap().parse::().unwrap();
let mut index = 0;
let stdin = io::stdin(); // 打开标准输入
while index < count {
let mut s = String::new();
stdin.read_line(&mut s).unwrap(); // 读取标准输入中的一行
println!("{}", s.trim()); // trim掉最后的\n
index += 1;
}
println!("Bye, world!");
}
然后在主进程中,循环向子进程的标准输入里写入消息,并读取标准输出的内容:
use std::process::{Command, Stdio};
use std::io::{BufRead, Write, BufReader};
fn main() {
// 消息数组
let msglist = ["msg1", "msg2", "msg3", "msg4", "msg5"];
// 启动子进程
let mut p = Command::new("../sub/target/debug/sub.exe")
.arg(msglist.len().to_string()) // 传递消息的个数
.stdin(Stdio::piped()) // 将子进程的标准输入重定向到管道
.stdout(Stdio::piped()) // 将子进程的标准输出重定向到管道
.spawn()
.unwrap();
let p_stdin = p.stdin.as_mut().unwrap();
let mut p_stdout = BufReader::new(p.stdout.as_mut().unwrap());
let mut line = String::new();
// 接收Hello world
p_stdout.read_line(&mut line).unwrap();
println!("{}", line);
// 循化发送消息
for msg in msglist.iter() {
// 发送消息
println!("write to stdid:{}", msg);
p_stdin.write(msg.as_bytes()).unwrap();
p_stdin.write("\n".as_bytes()).unwrap(); // 发送\n,子进程的read_line才会响应
// 接收消息
line.clear(); // 需要清空,否则会保留上次的结果
p_stdout.read_line(&mut line).unwrap();
println!("read from stdout:{}", line);
}
// 接收Bye world
line.clear();
p_stdout.read_line(&mut line).unwrap();
println!("{}", line);
// 等待子进程结束
p.wait().unwrap();
}