Go language中有这么一句口号: Do not communicate by sharing memory; instead, share memory by communicating.
这句话背后隐藏了一个当下比较流行的一种保证并发安全的方法: Message Passing. 在这种机制中, 线程通过相互发送数据以实现通信, 而不是通过传统的, 利用锁/信号量共享内存实现通信. 这就是Go那句口号的内涵.
这里, 我想通过Rust这门语言为例子, 介绍一下基于Channel的Message Passing.在Rust中, 标准库提供了Channel的实现. 你可以想像Channel在编程中就像河道一样, 如果你将一艘小船(消息)放在河道上, 下流就会接收到这条小船, 自然的, 实现了消息的传递.
一条Channel在编程中有两个部分组成, 一个是发送端, 另外一个就是接收端. 一般的, 我们将发送段记为tx
,接受端记为rx
. 发送端就对应河道的上游, 接收端就是下游. 在代码中, 发送端将数据传送出去, 另外一段代码负责监听我们的Channel, 一旦有信息到来就接收. 如果任何一端生命周期结束, Channel就会关闭.
废话少说, 我们来看看一个简单的例子: 多个线程发送数据, 主线程负责接收然后将消息打印出来.
use std::sync::mpsc;
use std::thread;
fn main() {
// 创建一个Channel, 返回一个pair, 第一个是发送端, 接下来是接收端.
let (tx, rx) = mpsc::channel();
for i in 0..10 {
// Rust使用移动语义, 需要拷贝一份发送端传送给新的线程.
let tx = mpsc::Sender::clone(&tx);
// 创建一个新的线程, 发送`i`
thread::spawn(move || {
tx.send(i).unwrap();
});
}
// 关闭发送端, 关闭channel
drop(tx);
// 打印放在缓冲队列中主线程接收到的数据
for msgs in rx {
println!("{}", msgs);
}
}
以下就是打印的无序结果
这样的好处有很多, 首先利用Rust的移动语义, 一旦将数据通过Channel传送出去了, 所有权就归接收端所有了, 发送端是没有办法修改读取发送过的数据的. 其次, 利用Channel, 我们可以很方便的实现一个线程池, 线程池的中的线程各包含一个接收端, 当没有任务时, 各个线程挂起等待, 一旦有任务送入(一个Lambda
表达式), 我们就执行这个任务.