自旋锁,它们有多有用?
您发现自己在代码中实际使用自旋锁的频率有多高?使用繁忙循环实际上优于使用锁的情况有多常见?
就我个人而言,当我编写某种需要线程安全的代码时,我倾向于使用不同的同步原语对其进行基准测试,就目前而言,使用锁似乎比使用自旋锁具有更好的性能。无论我实际持有锁的时间有多短,使用自旋锁时收到的争用量都远远大于使用锁时收到的争用量(当然,我在多处理器计算机上运行测试)。
我意识到在“低级”代码中更有可能遇到自旋锁,但我很想知道您是否发现它在更高级的编程中有用?
How often do you find yourself actually using spinlocks in your code? How common is it to come across a situation where using a busy loop actually outperforms the usage of locks?
Personally, when I write some sort of code that requires thread safety, I tend to benchmark it with different synchronization primitives, and as far as it goes, it seems like using locks gives better performance than using spinlocks. No matter for how little time I actually hold the lock, the amount of contention I receive when using spinlocks is far greater than the amount I get from using locks (of course, I run my tests on a multiprocessor machine).
I realize that it's more likely to come across a spinlock in "low-level" code, but I'm interested to know whether you find it useful in even a more high-level kind of programming?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
这取决于你在做什么。在一般应用程序代码中,您需要避免自旋锁。
在低级的东西中,您只需要保持几个指令的锁,并且延迟很重要,自旋锁可能是比锁更好的解决方案。但这种情况很少见,特别是在通常使用 C# 的应用程序中。
It depends on what you're doing. In general application code, you'll want to avoid spinlocks.
In low-level stuff where you'll only hold the lock for a couple of instructions, and latency is important, a spinlock mat be a better solution than a lock. But those cases are rare, especially in the kind of applications where C# is typically used.
在 C# 中,根据我的经验,“自旋锁”几乎总是比获取锁更糟糕 - 自旋锁的性能优于锁的情况很少见。
然而,情况并非总是如此。 .NET 4 正在添加 System.Threading .SpinLock结构。这在锁被持有很短时间并且被重复抓住的情况下提供了好处。来自 MSDN 文档 并行编程的数据结构:
在您执行诸如通过树锁定之类的操作的情况下,自旋锁可以胜过其他锁定机制 - 如果您仅在每个节点上锁定非常非常短的时间,则它们可以胜过传统锁。我在具有多线程场景更新的渲染引擎中遇到了这个问题,在某一时刻,旋转锁的性能优于 Monitor.Enter 的锁定。
In C#, "Spin locks" have been, in my experience, almost always worse than taking a lock - it's a rare occurrence where spin locks will outperform a lock.
However, that's not always the case. .NET 4 is adding a System.Threading.SpinLock structure. This provides benefits in situations where a lock is held for a very short time, and being grabbed repeatedly. From the MSDN docs on Data Structures for Parallel Programming:
Spin locks can outperform other locking mechanisms in cases where you're doing something like locking through a tree - if you're only having locks on each node for a very, very short period of time, they can out perform a traditional lock. I ran into this in a rendering engine with a multithreaded scene update, at one point - spin locks profiled out to outperform locking with Monitor.Enter.
对于我的实时工作,特别是设备驱动程序,我使用了相当多的它们。事实证明(当我上次计时时)等待同步对象(例如与硬件中断相关的信号量)至少会花费 20 微秒,无论中断发生实际需要多长时间。对内存映射硬件寄存器进行一次检查,然后检查 RDTSC(以允许超时,这样就不会锁定机器)处于高纳秒范围内(基本上降低了噪声)。对于根本不需要花费太多时间的硬件级握手来说,击败自旋锁确实很困难。
For my realtime work, particularly with device drivers, I've used them a fair bit. It turns out that (when last I timed this) waiting for a sync object like a semaphore tied to a hardware interrupt chews up at least 20 microseconds, no matter how long it actually takes for the interrupt to occur. A single check of a memory-mapped hardware register, followed by a check to RDTSC (to allow for a time-out so you don't lock up the machine) is in the high nannosecond range (basicly down in the noise). For hardware-level handshaking that shouldn't take much time at all, it is really tough to beat a spinlock.
我的 2c:如果您的更新满足某些访问条件,那么它们是很好的自旋锁候选者:
对于任何有可能产生收益的东西,您应该使用通知锁结构(事件、互斥体、信号量等)。
My 2c: If your updates satisfy some access criteria then they are good spinlock candidates:
For anything that has any potential to yield, you should use a notified lock structure (events, mutex, semaphores etc).
自旋锁的一个用例是,如果您期望争用非常少,但实际上将会有很多争用。如果不需要支持递归锁定,则可以在单个字节中实现自旋锁,并且如果争用非常低,则 CPU 周期浪费可以忽略不计。
对于实际用例,我经常拥有包含数千个元素的数组,其中可以安全地并行更新数组的不同元素。两个线程尝试同时更新同一元素的几率非常小(低争用),但我需要为每个元素加一把锁(我将拥有很多元素)。在这些情况下,我通常分配一个与我并行更新的数组大小相同的 ubyte 数组,并内联实现自旋锁(在 D 编程语言中):
另一方面,如果我必须使用常规锁,我必须分配一个指向对象的指针数组,然后为该数组的每个元素分配一个互斥对象。在上述场景中,这纯粹是一种浪费。
One use case for spin locks is if you expect very low contention but are going to have a lot of them. If you don't need support for recursive locking, a spinlock can be implemented in a single byte, and if contention is very low then the CPU cycle waste is negligible.
For a practical use case, I often have arrays of thousands of elements, where updates to different elements of the array can safely happen in parallel. The odds of two threads trying to update the same element at the same time are very small (low contention) but I need one lock for every element (I'm going to have a lot of them). In these cases, I usually allocate an array of ubytes of the same size as the array I'm updating in parallel and implement spinlocks inline as (in the D programming language):
On the other hand, if I had to use regular locks, I would have to allocate an array of pointers to Objects, and then allocate a Mutex object for each element of this array. In scenarios such as the one described above, this is just plain wasteful.
如果您有对性能至关重要的代码并且您已确定它需要比当前速度更快并且您已确定关键因素是锁定速度,那么它'尝试使用自旋锁是个好主意。在其他情况下,为什么还要麻烦呢?普通锁更容易正确使用。
If you have performance critical code and you have determined that it needs to be faster than it currently is and you have determined that the critical factor is the lock speed, then it'd be a good idea to try a spinlock. In other cases, why bother? Normal locks are easier to use correctly.
请注意以下几点:
大多数互斥体的实现在线程实际未调度之前会旋转一段时间。因此,很难将这些互斥锁与纯自旋锁进行比较。
多个线程在同一个自旋锁上“尽可能快地”旋转将占用所有带宽并大大降低程序效率。您需要通过在旋转循环中添加 noop 来添加微小的“睡眠”时间。
Please note the following points :
Most mutexe's implementations spin for a little while before the thread is actually unscheduled. Because of this it is hard to compare theses mutexes with pure spinlocks.
Several threads spining "as fast as possible" on the same spinlock will consome all the bandwidth and drasticly decrease your program efficiency. You need to add tiny "sleeping" time by adding noop in your spining loop.
您几乎不需要在应用程序代码中使用自旋锁,如果有的话您应该避免使用它们。
我没有任何理由在正常操作系统上运行的 C# 代码中使用自旋锁。繁忙的锁在应用程序级别上主要是一种浪费 - 旋转可能会导致您使用整个 cpu 时间片,而锁会在需要时立即导致上下文切换。
在某些情况下,拥有 nr 个线程 = nr 个处理器/核心的高性能代码可能会受益,但如果您需要该级别的性能优化,您可能会制作下一代 3D 游戏,在同步基元较差的嵌入式操作系统上工作,创建一个操作系统/驱动程序或在任何情况下不使用 c#。
You hardly ever need to use spinlocks in application code, if anything you should avoid them.
I can't thing of any reason to use a spinlock in c# code running on a normal OS. Busy locks are mostly a waste on the application level - the spinning can cause you to use the entire cpu timeslice, vs a lock will immediatly cause a context switch if needed.
High performance code where you have nr of threads=nr of processors/cores might benefit in some cases, but if you need performance optimization at that level your likely making next gen 3D game, working on an embedded OS with poor synchronization primitives, creating an OS/driver or in any case not using c#.
我在我的 HLVM 中使用了自旋锁来实现垃圾收集器的 stop-the-world 阶段项目,因为它们很简单,而且是一个玩具虚拟机。然而,在这种情况下,自旋锁可能会适得其反:
Glasgow Haskell 编译器的垃圾收集器中的一个性能错误非常烦人,以至于它有一个名字,“最后一次核心减速"。这是他们在 GC 中不当使用自旋锁的直接后果,并且由于其调度程序而在 Linux 上加剧,但事实上,只要其他程序竞争 CPU 时间,就可以观察到这种影响。
此处 可以看出,影响的不仅仅是最后一个核心此处,Haskell 程序在超过 5 个核心时发现性能下降。
I used spin locks for the stop-the-world phase of the garbage collector in my HLVM project because they are easy and that is a toy VM. However, spin locks can be counter-productive in that context:
One of the perf bugs in the Glasgow Haskell Compiler's garbage collector is so annoying that it has a name, the "last core slowdown". This is a direct consequence of their inappropriate use of spinlocks in their GC and is excacerbated on Linux due to its scheduler but, in fact, the effect can be observed whenever other programs are competing for CPU time.
The effect is clear on the second graph here and can be seen affecting more than just the last core here, where the Haskell program sees performance degradation beyond only 5 cores.
使用自旋锁时请始终牢记以下几点:
我个人见过很多死锁,只是因为有人认为使用自旋锁是个好主意。
使用自旋锁时要非常小心
(这一点我怎么强调都不为过)。
Always keep these points in your mind while using spinlocks:
I have personally seen so many deadlocks just because someone thought it will be a good idea to use spinlock.
Be very very careful while using spinlocks
(I can't emphasize this enough).