使用专门设计的螺旋锁(例如http://anki3d.org/spinlock)与这样的代码有什么好处:
std::mutex m;
while (!m.try_lock()) {}
# do work
m.unlock();
答案 0 :(得分:8)
在典型的硬件上,有很多好处:
你的天真“假螺旋锁”可能会在CPU旋转时使内部CPU总线饱和,使其他物理内核(包括持有锁的物理内核)匮乏。
如果CPU支持超线程或类似的东西,你的天真“假螺旋锁”可能会消耗物理核心上的过多执行资源,使共享该物理核心的另一个线程匮乏。
你天真的“假螺旋锁”可能会执行无关的写操作,从而导致错误的缓存行为。当您在x86 / x86_64 CPU上执行读取 - 修改 - 写入操作时(如try_lock可能执行的比较/交换),即使值未更改,它也始终写入。此写入会导致高速缓存行在其他核心上失效,要求它们在另一个核心访问该行时重新共享它。如果其他核心上的线程同时争用同一个锁,那就太糟糕了。
你天真的“假螺旋锁”与分支预测相互影响很大。当你最终获得锁定时,你将所有错误预测分支的母亲带到你锁定其他线程并需要尽快执行的位置。这就像一个跑步者全力以赴并准备好在起跑线上跑,但是当他听到起手枪时,他停下来喘口气。
基本上,该代码完全没错,因为自旋锁可能会出错。绝对没有什么是有效的。编写好的同步原语需要深入的硬件专业知识。
答案 1 :(得分:1)
使用自旋锁的主要好处是,如果最重要的前提条件成立,则获取和释放非常便宜:锁上很少或没有拥塞。
如果您充分肯定地知道不存在争用,那么自旋锁将大大优于互斥锁的一个天真实现,该互斥锁将通过库代码执行您不一定需要的验证,并进行系统调用。这意味着执行上下文切换(消耗数百个周期),并放弃线程的时间片并导致重新调度线程。这可能需要无限期的时间 - 即使锁定几乎立即可用,您仍然需要等待几十毫秒才能在不利条件下再次运行线程。
但是,如果没有争用的前提条件不成立,则自旋锁通常会因为没有进展而大大劣势,但它仍然消耗CPU资源,就像它正在执行工作一样。当在互斥锁上阻塞时,您的线程不会占用CPU资源,因此这些可以用于不同的线程来执行工作,或者CPU可以降低功耗,从而节省功耗。使用自旋锁是不可能的,它在做成功(或失败)之前正在进行“主动工作” 在最坏的情况下,如果服务器的数量大于CPU核心数,则自旋锁可能会导致巨大,性能不成比例,因为活动和运行的线程正在等待永远不会发生的情况在它们运行时发生(因为释放锁需要运行不同的线程!)。
另一方面,人们应该期望std::mutex
的每个现代无法实现已经包含一个微小的自旋锁,然后再回到做系统调用。但是......虽然这是一个合理的假设,但这并不能保证。
使用自旋锁支持std::mutex
的另一个非技术原因可能是许可条款。许可条款对于设计决策来说是一个很差的理由,但它们可能是非常真实的
例如,目前的GCC实现完全基于pthreads,这意味着使用标准线程库中的任何东西的“任何MinGW”必然与winpthreads链接(缺少备选方案)。这意味着您必须遵守winpthreads许可证,这意味着您必须复制其版权信息。对某些人来说,这是一个破坏者。