Reader 与 Writer

我们新设计的TicketStore能够运行,但其读取性能并不理想:同一时间只有一个客户端能读取特定的票证,因为Mutex<T>并未区分读者和写者。

我们可以通过使用另一种锁原语RwLock<T>来解决这个问题。RwLock<T>代表读写锁,它允许多个读者同时访问数据,但同一时间只允许一个写者进行操作。

RwLock<T>提供了两种获取锁的方法:readwriteread返回一个允许你读取数据的守卫,而write则返回一个允许你修改数据的守卫。

#![allow(unused)]
fn main() {
use std::sync::RwLock;

// 由读写锁保护的整数
let lock = RwLock::new(0);

// 获取读锁
let guard1 = lock.read().unwrap();

// 在第一个读锁仍激活的情况下
// 获取**第二个**读锁
let guard2 = lock.read().unwrap();
}

权衡取舍

表面上看,RwLock<T>似乎是不二之选:它提供了Mutex<T>功能的超集。如果可以使用RwLock<T>,为什么还要用Mutex<T>呢?

有两个关键原因:

  • 锁定RwLock<T>比锁定Mutex<T>成本更高。这是因为RwLock<T>需要追踪活跃读者和写者的数量,而Mutex<T>只需记录锁是否被持有。如果读取操作远多于写入操作,这种性能开销不是问题,但如果工作负载偏向写入,Mutex<T>可能是更好的选择。
  • RwLock<T>可能导致写者饥饿。如果有持续的读者等待获取锁,写者可能永远没有机会执行。RwLock<T>不对读者和写者获得锁的顺序提供任何保证,这取决于底层操作系统的实现策略,对写者可能不公平。

在我们的案例中,预期的工作负载偏向读取(因为大多数客户端将读取票证而非修改),因此RwLock<T>是一个合适的选择。