您多久发现自己在代码中实际使用自旋锁?遇到使用繁忙循环实际上优于使用锁的情况有多常见?
就个人而言,当我编写某种需要线程安全的代码时,我倾向于使用不同的同步原语对其进行基准测试,而且就目前而言,使用锁似乎比使用自旋锁提供更好的性能。不管我实际持有锁的时间有多短,我在使用自旋锁时收到的争用量远远大于我从使用锁中获得的量(当然,我在多处理器机器上运行我的测试)。
我意识到它更有可能在“低级”代码中遇到自旋锁,但我很想知道您是否发现它在更高级的编程中有用?
您多久发现自己在代码中实际使用自旋锁?遇到使用繁忙循环实际上优于使用锁的情况有多常见?
就个人而言,当我编写某种需要线程安全的代码时,我倾向于使用不同的同步原语对其进行基准测试,而且就目前而言,使用锁似乎比使用自旋锁提供更好的性能。不管我实际持有锁的时间有多短,我在使用自旋锁时收到的争用量远远大于我从使用锁中获得的量(当然,我在多处理器机器上运行我的测试)。
我意识到它更有可能在“低级”代码中遇到自旋锁,但我很想知道您是否发现它在更高级的编程中有用?
这取决于你在做什么。在一般应用程序代码中,您需要避免自旋锁。
在低级别的东西中,您只会持有几条指令的锁,并且延迟很重要,自旋锁垫是比锁更好的解决方案。但这些情况很少见,尤其是在通常使用 C# 的应用程序中。
在 C# 中,根据我的经验,“自旋锁”几乎总是比锁定更糟糕 - 这是一种罕见的情况,自旋锁会胜过锁。
然而,情况并非总是如此。.NET 4 正在添加一个System.Threading.SpinLock结构。这在锁被持有很短的时间并被反复抓住的情况下提供了好处。来自 MSDN docs on Data Structures for Parallel Programming:
在预期锁定等待时间较短的情况下,SpinLock 提供比其他形式的锁定更好的性能。
在您执行诸如通过树锁定之类的事情的情况下,自旋锁可以胜过其他锁定机制 - 如果您只在非常非常短的时间内对每个节点进行锁定,它们可以执行传统的锁定。我在具有多线程场景更新的渲染引擎中遇到了这个问题 - 有一次,自旋锁的性能优于 Monitor.Enter 的锁定。
对于我的实时工作,尤其是设备驱动程序,我已经使用了很多。事实证明(当我最后一次计时时)等待一个同步对象(如与硬件中断相关的信号量)至少需要 20 微秒,无论中断发生实际需要多长时间。对内存映射硬件寄存器的一次检查,然后是对 RDTSC 的检查(以允许超时,因此您不会锁定机器)处于高纳秒范围内(基本上在噪音中下降)。对于根本不需要太多时间的硬件级握手,击败自旋锁真的很难。
我的 2c:如果您的更新满足某些访问标准,那么它们是很好的自旋锁候选者:
对于任何有可能产生的东西,您应该使用通知锁结构(事件、互斥锁、信号量等)。
自旋锁的一个用例是,如果您期望争用非常低,但将会有很多争用。如果不需要支持递归锁定,可以在单个字节中实现自旋锁,如果争用非常低,那么 CPU 周期浪费可以忽略不计。
对于一个实际用例,我经常有数千个元素的数组,其中对数组不同元素的更新可以安全地并行发生。两个线程同时尝试更新同一个元素的几率非常小(低争用),但我需要为每个元素一个锁(我将拥有很多)。在这些情况下,我通常分配一个与我并行更新的数组大小相同的 ubytes 数组,并内联实现自旋锁(在 D 编程语言中):
while(!atomicCasUbyte(spinLocks[i], 0, 1)) {}
myArray[i] = newVal;
atomicSetUbyte(spinLocks[i], 0);
另一方面,如果我必须使用常规锁,我将不得不分配一个指向 Objects 的指针数组,然后为该数组的每个元素分配一个 Mutex 对象。在上述场景中,这简直是浪费。
如果您有性能关键代码,并且您确定它需要比当前更快,并且您确定关键因素是锁定速度,那么尝试自旋锁是个好主意。在其他情况下,为什么要打扰?普通锁更容易正确使用。
请注意以下几点:
大多数互斥锁的实现在线程实际未调度之前会旋转一段时间。因此,很难将这些互斥锁与纯自旋锁进行比较。
在同一个自旋锁上“尽可能快地”旋转的多个线程将占用所有带宽并大大降低您的程序效率。您需要通过在旋转循环中添加 noop 来添加微小的“睡眠”时间。
你几乎不需要在应用程序代码中使用自旋锁,如果有的话,你应该避免它们。
我没有任何理由在正常操作系统上运行的 c# 代码中使用自旋锁。繁忙的锁在应用程序级别上主要是浪费 - 旋转可能会导致您使用整个 cpu 时间片,而锁会在需要时立即导致上下文切换。
在某些情况下,拥有 nr 个线程 = nr 个处理器/内核的高性能代码可能会受益,但如果您需要在该级别进行性能优化,您可能会制作下一代 3D 游戏,在同步原语较差的嵌入式操作系统上工作,创建一个操作系统/驱动程序或在任何情况下不使用 c#。
使用自旋锁时,请始终牢记以下几点:
我个人见过这么多死锁,只是因为有人认为使用自旋锁是个好主意。
使用自旋锁时要非常小心
(我不能强调这一点)。