Skip to content

27.4 Send & Sync

下面来简单讲解一下 Rust 是如何实现免疫数据竞争的。Rust 线程安全背后的功臣是两个特殊的 trait。

  • std::marker::Sync

如果类型 T 实现了 Sync 类型,那说明在不同的线程中使用&T访问同一个变量是安全的。

  • std::marker::Send

如果类型 T 实现了 Send 类型,那说明这个类型的变量在不同的线程中传递所有权是安全的。

Rust 把类型根据 Sync 和 Send 做了分类。这样做起什么作用呢?当然是用在“泛型约束”中。 Rust 中所有跟多线程有关的 API,会根据情况,要求类型必须满足 Sync 或者 Send 的约束。这样一来,“孙猴子就永远也逃不出如来佛的手掌心”了。 你不可能随意在多线程之间共享变量,也不可能在使用多线程共享的时候忘记加锁。除非你使用 unsafe,否则不可能写出存在“数据竞争”的代码来。

比如我们最常见的创建线程的函数spawn,它的完整函数签名是这样的:

rust
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
    where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static

我们需要注意的是,参数类型 F 有重要的约束条件F: Send+'staticT: Send+'static。但凡在线程之间传递所有权会发生安全问题的类型,都无法在这个参数中出现,否则就是编译错误。 另外,Rust 对全局变量也有很多限制,你不可能简单地通过全局变量在多线程中随意共享状态。这样,编译器就会禁止掉可能有危险的线程间共享数据的行为。

在 Rust 中,线程安全是默认行为,大部分类型在单线程中是可以随意共享的,但是没办法直接在多线程中共享。 也就是说,只要程序员不滥用 unsafe,Rust 编译器就可以检查出所有具有“数据竞争”潜在风险的代码。 凡是通过了编译检查的代码,Rust 可以保证,绝对不会出现“线程不安全”的行为。如此一来,多线程代码和单线程代码就有了严格的分野。 一般情况下,我们不需要考虑多线程的问题。即便是万一不小心在多线程中访问了原本只设计为单线程使用的代码,编译器也会报错。

Released under the MIT License