Appearance
28.4 小结
Rust 语言本身并不知晓“线程”“并发”具体是什么,而是抽象出了一些更高级的概念 Send/Sync,用来描述类型在并发环境下的特性。 std::thread::spawn
函数就是一个普通函数,编译器没有对它做任何特殊处理。 它能保证线程安全的关键是,它对参数有合理的约束条件。这样的设计使得 Rust 在线程安全方面具备非常好的扩展性。 很多高级的并发模型,在 Rust 中都可以通过第三方库的形式实现,而且可以保证线程安全特性。 只要对外 API 设计得合理,客户就可以随便使用这些并行库,而不会有数据竞争的风险。
Rust 的这个设计实际上将开发者分为了两个阵营,一部分是核心库的开发者,一部分是业务逻辑开发者。 对于一般的业务开发者来说,完全没有必要写任何 unsafe 代码,更没有必要为自己的自定义类型去实现 Sync Send,直接依赖编译器的自动推导即可。 这个阵营中的程序员可以完全享受编译器和基础库带来的安全性保证,无须投入太多精力去管理细节,极大地减轻脑力负担。
而对于核心库的开发者,则必须对 Rust 的这套机制非常了解。比如,他们可能需要设计自己的“无锁数据类型”“线程池”“管道”等各种并行编程的基础设施。 这种时候,就有必要对这些类型使用 unsafe impl Send/Sync 设计合适的接口。这些库本身的内部实现是基于 unsafe 代码做的,它享受不到编译器提供的各种安全检查。 相反,这些库本身才是保证业务逻辑代码安全性的关键。
这个区分对整个生态圈是有好处的。跟前面讲的“内存安全”问题类似,需要使用 unsafe 的代码总是很小的一部分,把这部分代码抽象到独立的库里面,是比较容易测试和验证它的正确性的。 而业务逻辑代码才是千变万化的,我们千万不要在这部分代码中用 unsafe 做各种 hack。 因此,大部分普通人就能充分享受到少部分精英给我们提供的各种安全性保证(编译器加基础库),放心大胆地做各种并行优化,完全不必担心会制造线程安全问题。