设计回顾

让我们花点时间回顾一下走过的历程。

无锁与通道序列化

我们对多线程票证存储的第一个实现采用了以下方式:

  • 单一线程(服务器),持有共享状态,
  • 多个客户端通过他们自己的线程通过通道向其发送请求。

无需对状态加锁,因为只有服务器在修改状态。这是因为“收件箱”通道自然地序列化了进来的请求:服务器会逐一处理它们。我们已经讨论过这种方法在打补丁行为上的局限性,但我们尚未讨论原始设计的性能影响:服务器一次只能处理一个请求,包括读取。

细粒度加锁

随后,我们转向了一个更复杂的设计,其中每个票证都有自己的锁保护,客户端可以独立决定他们是想读取还是原子性地修改票证,获取适当的锁。

该设计允许更好的并行性(即,多个客户端可以同时读取票证),但本质上仍然是串行的:服务器逐一处理命令。特别是,它逐一给客户端分配锁。

我们能否完全移除通道,让客户端直接访问TicketStore,仅依靠锁来同步访问呢?

移除通道

我们需要解决两个问题:

  • 在线程间共享TicketStore
  • 同步访问存储

在线程间共享TicketStore

我们希望所有线程都指向同一个状态,否则实际上并没有形成多线程系统——我们只是并行运行多个单线程系统而已。我们已经在尝试跨线程共享锁时遇到过这个问题:我们可以使用Arc来解决。

同步访问存储

由于通道提供的序列化,还有一种交互仍然是无锁的:向存储中插入(或移除)票证。 如果我们移除通道,就需要引入(另一个)锁来同步对TicketStore本身的访问。

如果使用Mutex,那么再为每张票证添加额外的RwLock就没有意义了:Mutex已经序列化了对整个存储的访问,所以我们无论如何也无法并行读取票证。相反,如果使用RwLock,我们就能并行读取票证。我们只需在插入或移除票证时暂停所有读取即可。

让我们沿着这条路径看看它会带我们走向何方。