《Linux系统高效同步》SeqLock摘译
《Effective Synchronization on Linux/NUMA Systems》论文中提到了SeqLock,这个与StampedLock
有一定相关性,内容位于论文的第六节,做一下翻译和总结。

SeqLock是最具有扩展性的锁形式,如果面对大量的读请求,它会非常有用,读请求根本不需要向内存中发起写操作。读请求的发起者只需要关键部分(或者说同步逻辑)前后比较计数器,如果计数器没有更改,那就代表这个过程没有写发生,而之前关键部分的操作就是有效的,如果计数器发生了改变,那就废弃掉刚才的结果,重来一遍。
SeqLock包含一个自旋锁,该锁用来保障写请求之间的排他性。写请求如果获取到了锁,会增加计数器的值,该值会是一个奇数。读请求获取锁时,如果发现计数器是一个奇数的值,则代表写正在发生。当写请求操作完成,释放锁时,会再次增加计数器的值,使之成为一个偶数。锁的获取需要两个原子化的信号量操作,一个是用来获取自旋锁,另一个是计数器。当写请求完成,释放锁时,需要增加计数器,同时更改自旋锁的状态(以便于等待的线程能够获得通知进行锁的获取操作)。这意味着SeqLock的性能不如普通的自旋锁或者读写锁。
读请求可以通过循环执行关键部分来推迟这个过程,对于只有少量写时,是比较好的。在没有竞争的情况下,对于锁而言,知识两个内存读取操作和两个内存屏障。如果读请求在执行时,一个写请求也在同步进行,那么对于读而言,计数器必然不会相等,只需要重复执行关键部分即可。
SeqLock主要用在Linux中的时间获取操作上。对于安腾处理器,在读取时间信息时,会避免任何写请求,这使得时钟访问变得高度可扩展。所有的处理器能够维护它们各自的Cache Line。
这里就体现了乐观读的概念,如果计数器验证通过,一切都会发生在当前处理器的Cache Line上,确保了高性能,避免了同步的发生,而这些需要基于读远远大于写的前提下。
SeqLock的问题在于除了读取,读请求在关键部分中不能做其他事情,因为这个部分时需要重复执行的。读请求的关键部分会和写请求的执行内容产生竞争,因此对关键部分所涉及的数据结构、指针或分配内存都可能存在问题。
SeqLock所保护的数据需要使用者非常清楚。
就性能而言,这是一种理想的锁,因为读请求不需要一个排他的Cache Line。理想情况下,写请求会获取一个排他的Cache Line,其中包含了自旋锁和计数器,然后更新它们。
上述内容与
StampedLock
中的写状态设计有相关性,利用一个位来表示是否有写,方便了读请求的判断。同时StampedLock
又不限于此,写状态能够在奇偶变幻中体现出版本的概念,使得ABA问题不会出现。