什么使得自旋锁有效?

3

自旋锁只对具有真正并行处理能力的系统有效,即多核/处理器系统。这并不令人惊讶,因为这是它们设计的初衷。

尽管如此,共享资源的线程必须在不同的核心上执行。否则,情况就类似于单处理器系统。

这只是概率问题还是调度程序试图将互锁线程放在不同的CPU上以提供真正的并发性?

2个回答

5
线程上下文切换的代价是自旋锁可以有用的原因。通常引用的数字在Windows上为2000到10000个CPU周期。这是由于需要存储和重新加载处理器状态以及由于保证缓存未命中而导致的停顿所带来的成本。这是纯粹的开销,没有任何有用的工作得到完成。
因此,如果锁定预计很快可用,程序等待锁定变得可用会更好,烧掉几千个周期并定期尝试进入锁定。这避免了上下文切换。
没有可用于等待如此短时间的计时器,它是通过使用一个小循环实际烧掉周期来实现的。旋转。这在处理器级别得到支持,专用的PAUSE机器代码指令可用于减少在Intel / AMD核心上旋转时消耗的功率。.NET中的Thread.Yield()和Thread.SpinWait()利用了它。

实际上,在代码中有效地使用自旋锁可能会很棘手,当然,它仅在获取锁的几率足够高时才能很好地发挥作用。如果不是这样,自旋锁会对性能有负面影响,因为它会延迟上下文切换,而可能需要另一个线程重新获得处理器并释放锁。 .NET 4.0的SpinLock类很有用,它为失败的自旋生成ETW事件。否则很少有文档描述


仍不清楚是否存在一些特殊机制来调度交错线程到不同的核心。让两个线程在具有两个核心的CPU上竞争资源。如果两个线程在同一个核心上运行,那么旋转等待在50%的等待时间内是完全浪费的。因此,对于每种特定情况,存在阈值核心数,当旋转等待时平均保证获胜,否则效率低下。如果我的猜测正确,则代码应足够聪明,可以根据硬件选择锁定策略。如果CLR在单处理器系统上运行,是否有任何回退到通常的锁定? - Pavel Voronin
如果你的基础出了问题,那么你所能得到的收益微乎其微;在多线程编程中,不应该启动超过计算机核心数的线程。ThreadPool 已经积极采取了这种策略。Thread.SpinWait() 和 SpinLock 类都不会在单处理器系统上自旋。 - Hans Passant
无论如何,有成千上万的运行线程共享少量核心。我看不出100个和101个线程之间有什么区别。我错过了什么吗? - Pavel Voronin
如果你的程序启动了成千上万个线程,那么你做错了。整个操作系统通常只能管理一千个线程,而且它们都被阻塞了。 - Hans Passant
如果机器有许多占用核心的进程,那么你肯定会注意到程序变慢。当然这不是一个可以在软件中解决的问题,而是通过获取更好的机器或将耗费高昂的进程移动到另一台机器上来解决。 - Hans Passant
+1 尝试评估自旋锁的收益/损失是一个困难的问题,因为它严重依赖于通常不可预测和变化的其他负载。仅仅为了消除自旋锁的低效率而添加额外的核心,听起来本身就是一种资源浪费。我讨厌自旋锁 - 它们不仅浪费 CPU,还使用了线程持有锁可能需要用来完成工作并释放锁的内存带宽。 - Martin James

1
当涉及到两个线程时,自旋锁可能是有效的,因为有可能当一个线程在等待时,另一个线程会释放锁。因此,您正确地指出了没有任何保证,而且涉及到很多概率。因此,您只有在持有时间非常短的锁上使用自旋锁等待锁。因为正在获取锁的线程显然在获取锁时正在执行,所以如果该线程持有锁的时间非常短,那么该线程仍有很高的概率继续执行以释放锁。
但是,当涉及到IO时,自旋锁也可以是有效的,即当一个线程不是在等待另一个线程,而是在等待硬件事件发出信号表明数据即将到来时,特别是如果预计很快就会收到这些数据(例如等待执行硬件函数)。

不是真的,使用自旋锁等待 I/O 的情况非常罕见。这样的等待通常太长了,只能进行阻塞等待。驱动程序通常使用信号量来表示 I/O 完成(同时通过通过内核退出而不是直接中断返回请求调度运行)。自旋锁仅在保持时间非常短的多核系统上提供有效增益,从而减少争用的可能性以及 CPU/内存带宽浪费。 - Martin James
@MartinJames 这取决于硬件和我们讨论的情景。当然,如果等待远程 Web 服务器的响应时,你是完全正确的。但是,当要求获取一个板载传感器的值时,通常会使用自旋锁来等待答案。无论如何,我的主要观点不是自旋锁用于 IO,而是当自旋锁用于一个线程等待另一个线程时,OP 正确地指出,释放锁的线程是否真正在另一个核上执行,这纯粹是概率问题。 - Kris Vandermotten

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接