设计回顾
让我们花点时间回顾一下走过的历程。
无锁与通道序列化
我们对多线程票证存储的第一个实现采用了以下方式:
- 单一线程(服务器),持有共享状态,
- 多个客户端通过他们自己的线程通过通道向其发送请求。
无需对状态加锁,因为只有服务器在修改状态。这是因为“收件箱”通道自然地序列化了进来的请求:服务器会逐一处理它们。我们已经讨论过这种方法在打补丁行为上的局限性,但我们尚未讨论原始设计的性能影响:服务器一次只能处理一个请求,包括读取。
细粒度加锁
随后,我们转向了一个更复杂的设计,其中每个票证都有自己的锁保护,客户端可以独立决定他们是想读取还是原子性地修改票证,获取适当的锁。
该设计允许更好的并行性(即,多个客户端可以同时读取票证),但本质上仍然是串行的:服务器逐一处理命令。特别是,它逐一给客户端分配锁。
我们能否完全移除通道,让客户端直接访问TicketStore
,仅依靠锁来同步访问呢?
移除通道
我们需要解决两个问题:
- 在线程间共享
TicketStore
- 同步访问存储
在线程间共享TicketStore
我们希望所有线程都指向同一个状态,否则实际上并没有形成多线程系统——我们只是并行运行多个单线程系统而已。我们已经在尝试跨线程共享锁时遇到过这个问题:我们可以使用Arc
来解决。
同步访问存储
由于通道提供的序列化,还有一种交互仍然是无锁的:向存储中插入(或移除)票证。
如果我们移除通道,就需要引入(另一个)锁来同步对TicketStore
本身的访问。
如果使用Mutex
,那么再为每张票证添加额外的RwLock
就没有意义了:Mutex
已经序列化了对整个存储的访问,所以我们无论如何也无法并行读取票证。相反,如果使用RwLock
,我们就能并行读取票证。我们只需在插入或移除票证时暂停所有读取即可。
让我们沿着这条路径看看它会带我们走向何方。