【问题标题】:Is a spinlock lock free?自旋锁是免费的吗?
【发布时间】:2016-12-14 15:40:33
【问题描述】:

我对这两个概念有点困惑。

wiki 上无锁的定义:

如果有保证,非阻塞算法是无锁的 全系统进展

非阻塞的定义:

如果任何算法失败或暂停,则该算法称为非阻塞算法 线程不能导致另一个线程失败或挂起

我认为自旋锁是无锁的,或者至少是非阻塞的。但现在我不确定。因为根据定义,"spinlock is not lock-free" 对我来说也很有意义。就像,如果持有自旋锁的线程被挂起,那么它将导致其他在外面旋转的线程挂起。所以,根据定义,spinlock 甚至不是非阻塞的,更不用说无锁了。

我现在很困惑。谁能解释清楚?

【问题讨论】:

  • 自旋锁在旋转失败时无法回退到锁的通常名称是“bug”。
  • 是的,无锁是不幸的误称之一,它的含义与大多数人的想象完全不同。
  • @Voo:人们认为这意味着什么?免等待?或者这意味着避免任何称为“锁”的库实现(他们没有意识到用原子滚动自己的锁仍然是锁)?

标签: multithreading lock-free spinlock


【解决方案1】:

任何可以称为锁的东西(从临界区中排除其他线程,直到当前线程解锁)根据定义都不是无锁的。是的,自旋锁是一种锁。

如果一个线程在持有锁时休眠,则没有其他线程可以获取它并继续前进,而自旋锁无法阻止这种情况。操作系统可以随时取消调度线程,即使它位于关键部分的中间。


请注意,“无锁”与“无等待”不同,因此无锁算法仍然可以有 cmpxchg 重试循环之类的东西,但只要一个线程每次都成功,它就是锁免费。

无需等待的算法甚至无法做到这一点,最多只能等待缓存未命中/竞争原子操作的硬件仲裁。维基百科的non-blocking algorithm article 更详细地定义了无等待和无锁。


我认为您混淆了“阻塞”的两个定义。

我认为您在谈论一个spin_trylock 函数,该函数尝试获取自旋锁,如果失败而不是自旋,则返回错误。因此,这与非阻塞 I/O 具有相同意义的非阻塞:因错误而失败,而不是等待资源可用性。

这并不意味着系统中的任何线程都在在受自旋锁保护的事情上取得进展这只是意味着您的线程可以去做其他事情在重试之前,而不是需要使用单独的线程来并行执行某些操作以等待获取锁。


在无限循环中旋转算作阻塞/不进行。对于此定义,纯自旋锁与(在操作系统协助下)休眠直到另一个线程解锁的自旋锁之间没有区别。

无锁的定义与浪费 CPU 时间/功率来为独立工作腾出空间无关。


有点相关:获取非竞争自旋锁不需要系统调用,这意味着它是“轻量级”锁。即使在无竞争的情况下,某些锁实现也总是使用(相对较慢的)系统调用。请参阅Jeff Preshing's Always Use a Lightweight Mutex 文章。另请阅读 Jeff 的其他帖子以了解有关无锁编程的更多信息,因为它们非常出色。事实上,[lock-free] 标签 wiki 链接到它们非常好。

【讨论】:

  • @Mave:不,cmpxchg 尝试成功,除非有来自另一个线程的干扰。与在内存中等待特定值的自旋循环非常不同。 N 个线程以锁步方式尝试 cmpxchg 时,每次迭代至少有 1 个线程成功。一个线程不可能在使其他线程无法向前进展的状态下休眠,活锁也是不可能的。 CAS 重试循环在正式的 CS 术语中是“无锁”但不是“无等待”:en.wikipedia.org/wiki/Non-blocking_algorithm
  • @Mave: 不。如果 N 个线程同时尝试使用 xchg 进行自旋锁,它们都可能失败,任何线程都没有前进。因此,它是一种阻塞算法,与 CAS 重试循环不同,在 CAS 重试循环中,您尝试 CAS 的值来自上一次迭代的 CAS 读取。 (此外,这是实现自旋锁的一种糟糕的低效方式。自旋只读直到您看到它已解锁,这样您就没有 N 个线程向原子 RMW 操作发送垃圾邮件。请参阅 Locks around memory manipulation via inline assembly 以获取一个幼稚但不可怕的 x86 asm 示例。 )
  • @Mave:关键点:如果内存内容处于所有其他线程无限期卡住的状态,线程是否可以休眠或死亡?是:阻塞算法。否:无锁。 (但不一定无需等待。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-10
  • 2021-07-22
  • 1970-01-01
  • 2018-09-11
  • 1970-01-01
  • 2017-11-23
  • 2021-02-02
相关资源
最近更新 更多