高性能自旋锁 MCS Spinlock 的设计与实现

自旋锁(Spinlock)是一种在 Linux 内核中广泛运用的底层同步机制。排队自旋锁(FIFO Ticket Spinlock)是 Linux 内核 2.6.25 版本中引入的一种新型自旋锁,它解决了传统自旋锁由于无序竞争导致的“公平性”问题。但是由于排队自旋锁在一个共享变量上“自旋”,因此在锁竞争激烈的多核或 NUMA 系统上导致性能低下。MCS Spinlock 是一种基于链表的高性能、可扩展的自旋锁,本文详细剖析它的原理与具体实现。

一、引言

自旋锁(Spinlock)是一种在 Linux 内核中广泛运用的底层同步机制,长期以来,人们总是关注于自旋锁的安全和高效,而忽视了自旋锁的“公平”性。排队自旋锁(FIFO Ticket Spinlock)是内核开发者 Nick Piggin 在Linux Kernel 2.6.25[1] 版本中引入的一种新型自旋锁,它通过保存执行线程申请锁的顺序信息解决了传统自旋锁的“不公平”问题[4]。

排队自旋锁仍然使用原有的 raw_spinlock_t 数据结构,但是赋予 slock 域新的含义。为了保存顺序信息,slock 域被分成两部分,低位部分保存锁持有者的票据序号(Ticket Number),高位部分则保存未来锁申请者的票据序号。只有 Next 域与 Owner 域相等时,才表明锁处于未使用状态(此时也无执行线程申请该锁)。排队自旋锁初始化时 slock 被置为 0,即 Owner 和 Next 置为 0。内核执行线程申请自旋锁时,原子地将 Next 域加 1,并将原值返回作为自己的票据序号。如果返回的票据序号等于申请时的 Owner 值,说明自旋锁处于未使用状态,则直接获得锁;否则,该线程忙等待检查 slock 的 Owner 部分是否等于自己持有的票据序号,一旦相等,则表明锁轮到自己获取。线程释放锁时,原子地将 Owner 域加 1 即可,下一个线程将会发现这一变化,从忙等待状态中退出。线程将严格地按照申请顺序依次获取排队自旋锁,从而完全解决了“不公平”问题。

但是在大规模多处理器系统和 NUMA系统中,排队自旋锁(包括传统自旋锁)存在一个比较明显的性能问题:由于执行线程均在同一个共享变量 slock 上自旋,申请和释放锁的时候必须对 slock 进行修改,这将导致所有参与排队自旋锁操作的处理器的缓存变得无效。如果排队自旋锁竞争比较激烈的话,频繁的缓存同步操作会导致繁重的系统总线和内存的流量,从而大大降低了系统整体的性能。





回页首


二、MCS Spinlock 的原理

为了解决自旋锁可扩展性问题,学术界提出了许多改进版本,其核心思想是:每个锁的申请者(处理器)只在一个本地变量上自旋。MCS Spinlock[2] 就是其中一种基于链表结构的自旋锁(还有一些基于数组的自旋锁)。MCS Spinlock 的设计目标如下:

  1. 保证自旋锁申请者以先进先出的顺序获取锁(FIFO Ordering)。
  2. 只在本地可访问的标志变量上自旋。
  3. 在处理器个数较少的系统中或锁竞争并不激烈的情况下,保持较高性能。
  4. 自旋锁的空间复杂度(即锁数据结构和锁操作所需的空间开销)为常数。
  5. 在没有处理器缓存一致性协议保证的系统中也能很好地工作。

MCS Spinlock采用链表结构将全体锁申请者的信息串成一个单向链表,如图 1 所示。每个锁申请者必须提前分配一个本地结构 mcs_lock_node,其中至少包括 2 个域:本地自旋变量 waiting 和指向下一个申请者 mcs_lock_node 结构的指针变量 next。waiting 初始值为 1,申请者自旋等待其直接前驱释放锁;为 0 时结束自旋。而自旋锁数据结构 mcs_lock 是一个永远指向最后一个申请者 mcs_lock_node 结构的指针,当且仅当锁处于未使用(无任何申请者)状态时为 NULL 值。MCS Spinlock 依赖原子的“交换”(swap)和“比较-交换”(compare_and_swap)操作,缺乏后者的话,MCS Spinlock 就不能保证以先进先出的顺序获取锁,从而可能造成“饥饿”(Starvation)。


图 1. MCS Spinlock 示意图
Spinlock 示意图

MCS Spinlock 申请操作描述如下:

  1. 申请者 B 使用原子交换操作将自旋锁 mcs_lock 指向自己的mcs_lock_node 结构以确定在链表中的位置,并返回 mcs_lock原来的值 pre_mcs_lock。即使多个执行线程同时申请锁,由于交换操作的原子性,每个执行线程的申请顺序将会被唯一确定,不会出现不一致的现象。
  2. 如果 pre_mcs_lock 为 NULL,表明锁无人使用,B 立即成为锁的拥有者,申请过程结束。
  3. 如果 pre_mcs_lock 不为 NULL,则表明 pre_mcs_lock 指向申请者的直接前驱 A 的 mcs_lock_node 结构,因此必须通过pre_mcs_lock 来修改 A 的 next 域指向自己,从而将链表构建完整。
  4. 最后 B 在自己的mcs_lock_node 结构的 waiting 域上自旋。当 B 的直接前驱 A 释放自旋锁时,A 只须将 B 的 waiting 域修改为 0 即可。

MCS Spinlock 释放操作描述如下:





本文转自IBM Developerworks中国

      请点击此处查看全文

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值