Appearance
28.2 什么是 Sync
对应的,Sync 的定义是,如果类型 T 实现了 Sync trait,那说明在不同的线程中使用&T
访问同一个变量是安全的。 这句话也不好理解。下面我们仔细分析一下哪些类型是满足 Sync 约束的。
显然,基本数字类型肯定是 Sync。 假如不同线程都拥有指向同一个 i32 类型的只读引用&i32
,这是没什么问题的。因为这个类型引用只能读,不能写。多个线程读同一个整数是安全的。
大部分具有泛型参数的类型是否满足 Sync,很多都是取决于参数类型是否满足 Sync。 像Box<T>
、Vec<T>Option<T>
这种也是 Sync 的,只要其中的参数 T 是满足 Sync 的。
也有一些类型,不论泛型参数是否满足 Sync,它都是满足 Sync 的。这种类型把不满足 Sync 条件的类型用它包起来,就变成了满足 Sync 条件的。 Mutex<T>
就是这种。多个线程同时拥有&Mutex<T>
型引用,指向同一个变量是没问题的。
那么什么样的类型是!Sync
呢?所有具有“内部可变性”而又没有多线程同步考虑的类型都不是 Sync 的。比如,Cell<T>
和RefCell<T>
就不能是 Sync 的。 按照定义,如果我们多个线程中都持有指向同一个变量的&Cell<T>
型指针,那么在多个线程中,都可以执行Cell::set
方法来修改它里面的数据。而我们知道,这个方法在修改内部数据的时候,是没有考虑多线程同步问题的。 所以,我们必须把它标记为!Sync
。
还有一些特殊的类型,它们既具备内部可变性,又满足 Sync 约束,比如前面提到的Mutex<T>
类型。为什么说Mutex<T>
具备内部可变性? 大家查一下文档就会知道,这个类型可以通过不可变引用调用lock()
方法,返回一个智能指针MutexGuard<T>
类型,而这个智能指针有权修改内部数据。 这个做法就跟RefCell<T>
的try_borrow_mut()
方法非常类似。区别只是:Mutex::lock()
方法的实现,使用了操作系统提供的多线程同步机制,实现了线程同步,保证了异步安全;而 RefCell 的内部实现就是简单的普通数字加减操作。 因此,Mutex<T>
既具备内部可变性,又满足 Sync 约束。除了Mutex<T>
,标准库中还有RwLock<T>
、AtomicBool
、AtomicIsize
、AtomicUsize
、AtomicPtr
等类型,都提供了内部可变性,而且满足 Sync 约束。