Linux中的 mutex [二] —— 乐观自旋机制

本文基于 5.4.86 版本内核

mutex可视作是 spinlock 的可睡眠版本,同样是线程无法继续向前执行,但 spinlock 是"spin",导致该 CPU 上无法发生线程切换,而 mutex 是"block"(我们通常翻译成「阻塞」),可以发生线程切换,让所在 CPU 上的其他线程继续执行。阻塞既可以发生在线程试图获取 mutex 时,也可以发生在线程持有 mutex 时。

现在的 mutex 机制,要从这几方面纬度理解:

  1. optimistic spin 机制
  2. osq lock 机制(见前面文章《Linux中的锁机制 —— osq lock》《Linux中的spinlock机制[二] - MCS Lock》)
  3. handoff 机制
  4. mutex lock fast/mid/slow 流程
  5. 锁的交接过程

乐观自旋机制

在早期(2016年前)的mutex实现中,使用了一个单独的"count"来记录mutex的持有情况,为1表示空闲,0表示持有,负数表示有其他线程等待。不带乐观自旋的 mutex 版本中,若 A 线程持有锁且正在运行,此时 B 线程想拿锁,发现锁正被 A 拿着,B 将立即进入睡眠状态,在 A 线程释放锁后再去唤醒 B 线程来拿锁。但是我们通常认为持有锁的 A 若是在运行的情况下,通常会很快的退出临界区并释放锁,那么 B 去睡眠则是不必要的,毕竟睡眠和唤醒也是有代价的,完全可以多等一会即可。那么当 mutex 开启乐观自旋的 feature 后,若 B 拿不到锁,且发现锁的持有者 A 仍占用着 CPU 运行时,则不再去睡眠,而是像自旋锁一样进行自旋等待,直到 A 释放锁,但期间若 A 失去 CPU(即p->on_cpu为0)调度出去,那么 B 也不会继续傻傻的自旋等待,而是进入睡眠。

唤醒睡眠线程可能需要很长时间,一旦该线程开始运行,它可能会发现处理器缓存不包含任何数据,从而导致过时的缓存未命中。相反,spin 等待 mutex 的线程将能够在它可用时快速获取锁,并且可能仍然是 cache hot 的。很显然,乐观自旋机制能提高 mutex 和整体的性能。

本文会介绍 乐观自旋 和 wait list 和 osq list 如何工作的。

  • 只会有一个 task 进行 mutex 乐观自旋
  • osq list 上也只有一个任务在自旋
  • osq list 上的等待者结束自旋后会移入 wait list
  • wait list 上的第一个等待者也会进行乐观自旋

这里就有几个角色:
the first osq list waiter 较快获取锁
the other osq list waiter 慢
the first wait list waiter 较快获取锁
the other wait list waiter 慢
the mutex spiner 最快获取

mutex_spin_on_owner()
mutex_spin_on_owner()在给的 owner 上自旋,直到以下三个条件之一达成,就会退出自旋:

  • 如果 owner 不在 cpu 上运行了
  • 如果设置了重新调度请求标志
  • 如果 lock owner 发生了变化了

下图是一多任务竞争 mutex 的场景:

  1. cpu0 首先获取 mutex lock 进入临界区,cpu1 持有 osp lock 进行 mutex spin, cpu2 在尝试获取 cpu 1 的 osq lock 而 spin
  2. cpu0 释放 mutex lock, cpu1 获取 mutex lock 进入临界区
  3. cpu1 task 调度出去,正在睡眠;cpu 2 的 osq list 的等待者不再 spin,而加入 wait list 成为第一等待者;cpu3 也加入 wait list 成为第二等待者
  4. cpu1 唤醒后,继续执行临界区代码,再释放 mutex lock,cpu2 获取 mutex 进入临界区
  5. cpu2 释放 mutex lock,cpu3 获取 mutex 进入临界区

可以认为 mutex 整体 3 个路径:
fast path: 直接获取锁
mid path: mutex spin; osq spin; osq list waiter
slow path: wait list 等待
在这里插入图片描述
看下图,我们来看一个多 task 交错获取 mutex 的场景。

  1. cpu0 fastpath 获取 mutex
  2. cpu1 在 mutex spin 等待 cpu0,cpu2 在 osq list spin 等待 cpu0,但是 cpu1 受到 reshedule 请求,所以只能调度出去
  3. cpu2 在 midpath 的 osq_lock list 里 spinning, 由于 cpu1 unlock osq_lock 并调度出去,cpu2 此时获取 osq_lock 进入 mutex spin 等待 cpu0 释放 mutex,此时 cpu2 是第一继承人
  4. cpu0 释放 mutex, cpu2 获取该 mutex 进入临界区
  5. cpu2 释放 mutex, 唤醒 wait list,其中 cpu1 退出 mutex spin 后,就加入了 wait list,并且它成为 wake list 的第一个 waiter,cpu2 通过 handoff 机制把 mutex 递交给 cpu1
  6. cpu1 获取 mutex 并进入临界区,cpu3 此时在 mutex spinning 等待 cpu1
  7. cpu1 释放 mutex,cpu3 立刻获取到 mutex
  8. cpu3 获取 mutex 并进入临界区,而后释放 mutex
    在这里插入图片描述
    参考:http://jake.dothome.co.kr/mutex/
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值