其他分享
首页 > 其他分享> > 自旋锁与互斥锁

自旋锁与互斥锁

作者:互联网

前言

在编程中经常需要使用到互斥. 互斥就是, 这个事情只能有一个人干, 我正在做着的时候, 别人要想做这件事就得等我做完了.

互斥的实现是通过锁的机制, 也就是我把这块锁上了, 别人就进不来了, 等我做完再把锁释放掉.

但是, 前辈们已经证明了, 要想单纯的在软件层面上实现锁的机制是很难的, 即使是简单的一条加1的操作, 在CPU执行时也需要如下几步:

  1. 将变量从内存读到寄存器
  2. 寄存器中的值加1
  3. 将寄存器中的值写回内存

而中间任何一步发生切换, 都可能导致锁机制的失败. 因此, 在软件层面的实现代价是很高的(感兴趣的可搜一下: Peterson 算法).

造成其困难的原因是什么呢? 因为无法保证单条 CPU 指令的原子性. 既然软件不够, 那就硬件来凑咯.

于是, CPU提供了lock指令, 可以保证单条指令的原子性. 而有了硬件的支持,锁的实现就简单的多了. 比如在有一条指令xchg用来对两个变量进行交换, 那么就可以将锁放到一个全局变量中, 规定谁换到锁了就持有, 用完再放回去. 很简单的实现了锁的机制. (至于硬件上是如何实现的, 我确实不甚了解, 因此这里按下不表)

好, 现在能够很容易的实现锁了, 但是既然能拿到锁, 那也就有可能拿不到锁. 如果没有拿到锁, 怎么办呢? 两种应对方案既: 自旋/互斥, 他们也是其他锁(读写锁/乐观锁/悲观锁)的底层实现.

自旋锁

在获取锁失败的情况下, 立刻再次尝试获取. 大概这样:

int locked = 0;
void lock() {
    // 将1放入 locked 变量
    // 若 locked 中存放的是 1, 则说明当前已经有其他人获取了, 继续等待
    while (xchg(&locked, 1)) ;
}
void unlock() {
    // 使用完后, 将变量置为0
    xchg(&locked, 0);
}

既, 线程会不停的尝试获取锁.

但是, 忙等会导致如下问题:

互斥锁

自旋锁的问题其实就出在忙等上, 假设没有拿到锁的话, 就将线程暂时休眠, 等到锁被释放了再将其唤醒, 这样不就能够避免性能的浪费了嘛.

但是, 线程的调度靠线程自己是无法完成的. 需要操作系统帮忙调度.

既然进行了线程调度, 那必然就需要进行线程的上下文切换了.

但是, 如果锁的占用时间比线程上下文切换的时间还要短呢? 这边线程上下文切换还没完成, 那边锁已经释放了, 这不就会导致运行效率的降低了么.

结合

自旋锁的问题是忙等会浪费 CPU 性能, 而互斥锁的问题是若锁的持有时间极短会导致运行效率的降低.

也就是说

那么有没有一种既不会浪费 CPU 性能, 又不会降低线程运行效率的办法呢? 有,

  1. 通过自旋尝试获取锁
  2. 若获取失败, 则转为互斥

这样可以令大部分情况在首次获取锁时便能拿到, 无需线程切换. 在较少的情况下, 会造成部分性能的浪费. 但是整体性能是提高了的.

最后, 我们在日常上层开发的时候, 其实很少考虑获取锁的实现方式是自旋还是互斥, 更多考虑的是读写锁还是什么. 底层已经为我们选择了最合适的方式.

标签:locked,获取,互斥,线程,自旋,CPU
来源: https://www.cnblogs.com/hujingnb/p/16220962.html