您经常在代码中发现自己使用自旋锁吗?遇到使用繁忙循环的情况实际上优于锁的使用情况有多常见? 就个人而言,当我编写某种需要线程安全的代码时,我倾向于使用不同的同步原语对其进行基准测试,并且就其而言,使用锁似乎比使用自旋锁具有更好的性能。无论我实际持有锁的时间有多少,使用自旋锁时我收到的争用量远大于使用锁时获得的数量(当然,我在多处理器机器上运行我的测试)。
我意识到在“低级”代码中更有可能遇到一个自旋锁,但是我很想知道你是否觉得它在更高级别的编程中是否有用?
答案 0 :(得分:32)
这取决于你在做什么。在一般的应用程序代码中,您需要避免使用自旋锁。
在低级别的东西中,你只需要锁定几条指令,并且延迟很重要,螺旋锁是一种比锁更好的解决方案。但这些情况很少见,特别是在通常使用C#的应用程序中。
答案 1 :(得分:20)
在C#中,根据我的经验,“旋转锁定”几乎总是比锁定更糟糕 - 旋转锁定优于锁定的情况很少发生。
然而,并非总是如此。 .NET 4正在添加System.Threading.SpinLock结构。这在锁定被保持很短时间并被反复抓取的情况下提供了益处。来自Data Structures for Parallel Programming上的MSDN文档:
在预期等待锁定的情况下,SpinLock提供比其他锁定形式更好的性能。
如果您正在执行诸如锁定树之类的操作,旋转锁可以胜过其他锁定机制 - 如果您在非常短的时间内只对每个节点进行锁定,则它们可以执行传统操作锁。我在一个带有多线程场景更新的渲染引擎中遇到了这个问题 - 在某一点上 - 使用Monitor.Enter将自旋锁分析为优于锁定。
答案 2 :(得分:11)
对于我的实时工作,特别是设备驱动程序,我已经使用了它们。事实证明(当我最后一次定时)等待与硬件中断相关的信号量这样的同步对象咀嚼至少20微秒,无论中断实际发生多长时间。对存储器映射的硬件寄存器进行单次检查,然后检查RDTSC(允许超时以便不锁定机器)处于高纳秒范围内(基本上在噪声中)。对于不应该花费太多时间的硬件级握手,要击败自旋锁是非常困难的。
答案 3 :(得分:9)
我的2c:如果你的更新符合某些访问标准,那么它们就是好的螺旋锁候选者:
对于任何有可能屈服的东西,你应该使用一个通知的锁结构(事件,互斥,信号量等)。
答案 4 :(得分:7)
旋转锁的一个用例是,如果你期望非常低的争用,但会有很多。如果您不需要支持递归锁定,则可以在单个字节中实现自旋锁,如果争用非常低,则CPU周期浪费可以忽略不计。
对于实际用例,我经常有数千个元素的数组,其中对数组的不同元素的更新可以安全地并行发生。两个线程试图同时更新同一个元素的几率非常小(低争用)但我需要为每个元素锁定一个(我将会有很多)。在这些情况下,我通常会分配一个与我正在并行更新的数组大小相同的ubytes数组,并实现内联的自旋锁(在D编程语言中):
while(!atomicCasUbyte(spinLocks[i], 0, 1)) {}
myArray[i] = newVal;
atomicSetUbyte(spinLocks[i], 0);
另一方面,如果我必须使用常规锁,我将不得不分配一个指向Object的指针数组,然后为该数组的每个元素分配一个Mutex对象。在如上所述的场景中,这只是一种浪费。
答案 5 :(得分:5)
如果您有性能关键代码和,您已确定它需要比当前和更快,您已确定关键因素是锁定速度,那么试一下自旋锁是个不错的主意。在其他情况下,为什么要这么麻烦?普通锁更容易正确使用。
答案 6 :(得分:4)
请注意以下几点:
大多数互斥锁的实现在线程实际未调度之前会旋转一段时间。因此,很难将这些互斥锁与纯自旋锁进行比较。
在同一个螺旋锁上“尽可能快地”旋转的几个线程会占用所有带宽并大大降低程序效率。你需要通过在你的旋转循环中添加noop来增加微小的“休眠”时间。
答案 7 :(得分:3)
你几乎不需要在应用程序代码中使用自旋锁,如果你应该避免使用它们。
我无法在正常操作系统上运行的c#代码中使用自旋锁。繁忙的锁大多是应用程序级别的浪费 - 旋转可以使您使用整个cpu时间片,而锁定将立即导致上下文切换(如果需要)。
高性能代码,你有nr个线程= nr个处理器/核心在某些情况下可能会受益,但是如果你需要在那个级别进行性能优化你可能正在制作下一代3D游戏,那么在同步原语很差的嵌入式操作系统上工作,创建一个OS /驱动程序,或者在任何情况下都不使用c#。
答案 8 :(得分:3)
我在我的HLVM项目中使用自旋锁来处理垃圾收集器的世界各个阶段,因为它们很简单,而且它是一个玩具虚拟机。但是,在这种情况下,自旋锁会适得其反:
格拉斯哥Haskell编译器的垃圾收集器中的一个漏洞是非常烦人的,它有一个名称,“last core slowdown”。这是他们在GC中不恰当地使用自旋锁的直接结果,并且由于其调度程序而在Linux上被激活,但事实上,只要其他程序竞争CPU时间,就可以观察到这种效果。
第二个图here上的效果很明显,可以看到影响的不仅仅是最后一个核心here,其中Haskell程序的性能下降超过了5个核心。
答案 9 :(得分:0)
使用自旋锁时,请始终牢记以下几点:
我个人看到如此多的死锁,只是因为有人认为使用自旋锁是一个好主意。
使用自旋锁时要非常小心
(我对此不太强调)。