go 语言面试中,经常会被问到 go 语言的优势,go 的高并发能力便是优势之一。那么,rust 的并发如何呢?
use std::thread;
fn main() {
let handler = thread::spawn(|| {
// thread code
println!("thread");
});
println!("main");
handler.join().unwrap();
}
我们调用 thread::spawn 函数来创建线程,它接收一个闭包函数作为参数,函数类型实现了 FnOnce,闭包中实现异步执行的代码。FnOnce特性闭包只能被执行一次。
Go语言开启协程也使用了闭包,但它依赖 sync.WaitGroup对象来等待多个协程执行结束,相当于依赖了一个独立的三方,模式上属于1:N 的控制。
而 rust 依赖 spawn 的返回值来等待线程执行结束,它返回 JoinHandle 类型对象,调用它的 join 方法可以阻塞当前线程直到对应的异步线程运行结束。但每次仅仅只能管控一个异步的线程,模式上属于 1:1 的控制。
Unwrap() 属于处理 result 类型的快捷方法。如果 result 是成功的结果,unwrap 也会返回成功的结果。如果是错误的结果,方法会发生 panic。常见的还有 expect() 方法,可以给 panic 指定错误信息。
Go 语言的 channel 通讯的口号:Do not communicate by sharing memory; instead, share memory by communicating,rust 中 channel 也展示了同样的设计理念。
use std::sync::mpsc::channel;
let (sender, receiver) = channel();
rust也可以通过 channel 在线程间通讯, channel 函数会返回 2 个对象,消息的发送者 Sender 和接收者 Receiver。Rust 可以推断出 channel 的具体类型,当然也可以明确类型声明。
Doc下示例代码,对于 std::sync::mpsc 中 mpsc 的全称描述: Multi-producer, single-consumer FIFO queue communication primitives. 多生产者,单消费者。
use std::thread;
use std::sync::mpsc::channel;
fn main() {
let (sender, receiver) = channel::<String>();
thread::spawn(move || {
sender.send("Hello".to_string()).unwrap();
});
// Let's see what that answer was
println!("receive:{:?}", receiver.recv().unwrap());
}
因为有所有权转移的限制,Sender 可以克隆多个副本,并在不同的线程中向 channel 写入,但 Receiver 没有实现 Clone 特性,有且只能存在一个。如果要实现多个线程从同一个通道接收值,就需要使用 Mutex。
下面是官方提供的 channel 示例,这个例子丰富地演示了 channel 通讯的细节。
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;
static NTHREADS: i32 = 3;
fn main() {
// Channels have two endpoints: the `Sender` and the `Receiver`,
// where `T` is the type of the message to be transferred
// (type annotation is superfluous)
let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel();
let mut children = Vec::new();
for id in 0..NTHREADS {
// The sender endpoint can be copied
let thread_tx = tx.clone();
// Each thread will send its id via the channel
let child = thread::spawn(move || {
// The thread takes ownership over `thread_tx`
// Each thread queues a message in the channel
thread_tx.send(id).unwrap();
// Sending is a non-blocking operation, the thread will continue
// immediately after sending its message
println!("thread {} finished", id);
});
children.push(child);
}
// Here, all the messages are collected
let mut ids = Vec::with_capacity(NTHREADS as usize);
for _ in 0..NTHREADS {
// The `recv` method picks a message from the channel
// `recv` will block the current thread if there are no messages available
ids.push(rx.recv());
}
// Wait for the threads to complete any remaining work
for child in children {
child.join().expect("oops! the child thread panicked");
}
// Show the order in which the messages were sent
println!("{:?}", ids);
}
Go 的 channel 有带 buffer 和不带 buffer 两种情况,不支持我们向 channel 中无限写入。但 rust 的 channel 居然支持无限写入,如果 receiver 处理不过来,会导致消息在内存中堆积。
我们先从如何使用并发谈起,接下来会列举几个例子来说明 rust 并发编程