我已经向D编程语言运行时的维护者建议了几次内存分配器/垃圾收集器应该使用自旋锁而不是常规的OS关键部分。这并没有真正流行起来。以下是我认为自旋锁更好的原因:
不在内存分配器/垃圾收集器实现中使用自旋锁有什么好的理由吗?
答案 0 :(得分:3)
显然,自旋锁的最坏情况行为很糟糕(操作系统调度程序只看到30个CPU绑定线程,所以它试图给它们一些CPU时间;其中29个像疯了一样旋转而持有锁睡觉),所以如果可以,你应该避免它们。由于这个原因,很多人比我更聪明地声称自旋锁具有 no 用户空间用例。
系统互斥锁应该在线程进入休眠状态之前旋转一点(或者确实进行任何类型的系统调用),因此即使存在一些争用,它们有时也可以执行与自旋锁完全相同的操作。
< / LI>分配器通常可以通过仅使用锁来将页面分配给线程来实际消除锁争用。然后每个线程负责分区自己的页面。您最终每N次分配只获取一次锁定,并且您可以将N配置为您喜欢的任何内容。
我认为2和3是强有力的论据,无法通过综合基准有效抵消。您需要证明现实世界程序的性能受到影响。
答案 1 :(得分:2)
Spinlocks在只有一个CPU /核心的系统上绝对没用,或者 - 更常见的 - 在高争用情况下(当你有很多线程等待锁定时)。
答案 2 :(得分:2)
无论如何,在Windows上,关键部分对象已经可以选择执行此操作(http://msdn.microsoft.com/en-us/library/ms682530.aspx):
线程使用InitializeCriticalSectionAndSpinCount或SetCriticalSectionSpinCount函数指定临界区对象的旋转计数。旋转意味着当线程尝试获取被锁定的关键部分时,线程进入循环,检查锁是否被释放,如果未释放锁,则线程进入休眠状态。在单处理器系统上,旋转计数被忽略,临界区旋转计数被设置为0(零)。在多处理器系统上,如果临界区不可用,则在对与临界区关联的信号量执行等待操作之前,调用线程会旋转dwSpinCount次。如果临界区在旋转操作期间变为空闲,则调用线程将避免等待操作。
希望其他平台也会效仿,如果他们还没有。
答案 3 :(得分:2)
有没有充分的理由不在内存分配器/垃圾收集器实现中使用自旋锁?
当某些线程受计算限制(受CPU限制)且其他线程受内存分配器限制时,则使用自旋锁需要CPU周期,否则可由计算绑定线程使用和/或由线程使用属于其他过程。
答案 4 :(得分:0)
不确定我是否同意,因为内存分配可能需要很长时间(如果您预先分配所有内存然后重新发出它,它就不会这样做。)你真的需要尝试使用多个相同的分配和解除分配具有数百万条目的gig堆大小,许多应用程序达到分配关键部分(注意应用程序而不是线程),并且磁盘从没有足够的内存中删除/交换。您还可以在分配期间获得磁盘交换问题,并且等待磁盘请求进行自旋锁肯定是不合适的。
正如CyberShadow在单线程CPU上提到的那样,你最终会进入正常的锁定开销。现在,一种语言可以运行在许多嵌入式CPUS上,这些CPUS都是单线程的。
此外,如果您可以使用互锁交换,那是最好的(因为它无锁,但仍然会停止CPU并为多核内存提升LOCK#)但是大多数锁仍然使用它(但需要做更多)。但是,堆的结构通常意味着互锁的交换是不够的,您最终会创建一个关键部分。请注意,在带有GC的(分代)Mark Sweep Nursery中,可以将分配作为互锁比较和添加指针。我为Cosmos C#OS GC执行此操作,它可以进行堆栈速度分配。
答案 5 :(得分:0)
格拉斯哥Haskell编译器的垃圾收集器中的一个漏洞是非常烦人的,它有一个名称,“last core slowdown”。这是他们在GC中不恰当地使用自旋锁的直接结果,并且由于其调度程序而在Linux上被激活,但事实上,只要其他程序竞争CPU时间,就可以观察到这种效果。
第二个图here上的效果很明显,可以看到影响的不仅仅是最后一个核心here,其中Haskell程序的性能下降超过了5个核心。