作者:谢经纬,人称“刀哥”,20年IT从业者,数据通信网络专家,电信网络架构师,现任Netwarps开发总监。刀哥在操作系统、网络编程、高并发、高吞吐量、高可用等领域有多年的实践经验,对网络和编程方面的新技术有着浓厚的兴趣。现代CPU基本上都是多核结构。为了充分利用多核的能力,多线程是一个绕不开的话题。无论是同步编程还是异步编程,多线程相关的问题一直都是比较棘手和容易出错的。本质上,正是由于多线程程序的复杂性,尤其是竞争条件的错误,导致错误的发生具有一定的随机性。随着程序规模的增大,求解问题的难度也随之增大。其他语言C/C++的做法,把同步互斥和线程通信的问题全部留给了程序员。关键共享资源通常需要通过同步原语(例如Mutex/Semaphone/CondVariable)来保护。简单地说,需要锁。但是,如何添加,添加到哪里,如何发布,都是程序员的自由。不加也可以运行,大部分时候不会出问题。当程序的负载上来的时候,不经意间程序就崩溃了,然后就是痛苦的寻找问题的过程。Go通过channels提供消息机制来规范协程之间的通信,但是对于共享资源,其方式与C/C++并无区别。当然,遇到的问题都是类似的。Rust的方法类似于Go。Rust还提出了线程间通信的通道机制。因为Rust的所有权关系,不可能同时持有多个变量引用,所以channel分为rx和tx两部分,不像Go那么直观易用。实际上,通道的内部实现也使用了原子操作和同步原语来封装共享资源。所以,问题的根源还是在于Rust如何操作共享资源。Rust通过所有权和类型系统提供了一种不同的解决问题的方法。共享资源的同步互斥不再是程序员的选择。Rust代码中与同步和互斥相关的并发错误是编译时错误。程序员在开发过程中写出正确的代码远比在生产环境中顶着压力排查问题的困境要好得多。让我们来看看这一切是如何完成的。什么是发送、同步?Rust语言级别通过std::marker提供了两个Traits,Send和Sync。一般而言,Send标志表示类型的所有权可以在线程之间转移,Sync标志表示实现Sync的类型可以在多个线程中安全地拥有对其值的引用。这段话非常混乱。为了更好地理解Send和Sync,我们需要看一下这两个约束是如何使用的。下面是标准库中std::thread::spawn()的实现:pubfnspawn(self,f:F)->io::Result>whereF:FnOnce()->T,F:Send+'static,T:Send+'static,{unsafe{self.spawn_unchecked(f)}}可以看到创建线程需要提供闭包,约束这个闭包的都是Send,也就是需要传给线程,闭包返回值T的约束也是Send(这个不难理解,线程运行完返回值需要传回去).例如,以下代码无法编译。让a=Rc::new(100);leth=thread::spawn(移动||{letb=*a+1;});h.join();编译器指出std::rc::Rc不能在线程之间安全地发送。原因是闭包的实现在内部让编译器创建了一个匿名结构,捕获的变量存储在其中。上面代码的闭包大致翻译为:struct{a:Rc::new(100),...}而Rc是不支持Send的数据类型,所以匿名结构,即,闭包,不支持Send,无法满足std::thread::spawn()对F的约束。如果上面的代码改用Arc,编译会通过,因为Arc是一个数据支持发送的类型。但是Arc不允许共享可变引用。如果要修改多个线程之间的共享资源,需要用Mutex来包裹数据。代码会变成这样:letmuta=Arc::new(Mutex::new(100));leth=thread::spawn(move||{letmutshared=a.lock().unwrap();*shared=101;});h.join();为什么Mutex可以做到这一点?您可以使用RefCell来完成相同的功能吗?答案是否定的。我们来看看这些数据类型的限制:unsafeimplSendforArc{}unsafeimplSyncforArc{}unsafeimplSendforRefCellwhereT:Send{}impl!SyncforRefCell{}unsafeimplSendforMutex{}unsafeimplSyncforMutex{}Arc可以发送,当包装的T支持发送和同步时。显然Arc>不满足这个条件,因为RefCell不支持Sync。在wrappedT支持Send的前提下,Mutex同时满足Send和Sync。其实Mutex的作用就是把一个支持Send的普通数据结构转换成支持Sync的,然后就可以通过Arc传递给线程了。我们知道在多线程下访问共享资源是需要加锁的,所以Mutex::lock()就是这样一个操作,l??ock()之后获取的是内部数据的变量引用。通过上面的分析,我们可以看出Rust另辟蹊径,利用所有权和Type系统来解决编译时多线程共享资源的问题,这确实是一个巧妙的设计。异步代码,协程异步代码中的同步互斥问题与同步多线程代码没有本质区别。异步运行时库通常提供类似于std::thread::spawn()的方法来创建协程/任务。以下是async-std创建协程/任务的API:pubfnspawn(future:F)->JoinHandlewhereF:Future