9阅网

您现在的位置是:首页 > 知识 > 正文

知识

multithreading - 为什么自旋锁会成为多线程程序的性能问题?

admin2022-10-21知识19

我知道什么是自旋锁,也知道它们使用繁忙等待。但为什么在多核处理器上的多线程程序中会成为性能问题呢?

又该如何解决呢?



【回答】:

你的第一个问题,是在自旋锁保护的部分变得满足的情况下,通常是有更多的线程准备执行,而不是你有可用的内核。这意味着每个线程在自旋锁中浪费的时间都有可能让另一个线程饿肚子,而另一个线程本来是有适当的事情要做的。

还有就是自旋锁本身的成本。你正在消耗你的内存事务预算,而这个预算实际上是在处理器核心之间共享的。实际上,这可能导致关键部分内的操作变慢。

一个很好的例子就是Windows内核中的内存分配器,在1703和1803之间的版本中。在有超过16个线程的系统中,一旦超过了CPU总利用率的50%,该路径中的自旋锁就会失去控制,并开始占用90%的CPU时间。由于竞争的线程烧掉了内存带宽,在关键部分里面花费的时间增加了十倍以上。


天真的解决方案是在自旋周期之间使用纳米休眠,以便至少减少锁本身所消耗的性能。但这也是非常糟糕的,因为核心仍然处于阻塞状态,不做任何实际工作。

试着在自旋锁中屈服代替?只是转得更慢,你最终会得到一个与操作系统的调度率成正比的最小延迟。按照1ms(Windows实时模式,任何进程请求时都会激活)、5ms(Linux默认的200Hz调度器)、10ms(Windows默认模式)的速率,这是把巨大的延迟引入执行中。而如果你恰好又碰到了关键部分,那就太浪费了,因为你现在增加了上下文切换的开销,却没有任何收益。


最终,使用操作系统基元来处理关键部分。常见的方法是使用原子操作来实现。探测 是否发生了争用,当发生了争用,才会涉及到操作系统。

无论哪种方式,下面的操作系统都有比较好的手段来解决争用问题,主要是以等待列表的形式出现。意思是说,在semaphore上等待的线程,只有在允许恢复的时候,才会准确的等待起来,并且保证持有相应的锁。当离开争用区域时,拥有锁的线程会通过轻量级的方式检查是否有过争用,只有在有争用的情况下才会通知操作系统恢复对其他线程的操作。


并不是说你真的应该重新发明轮子......不过,在Windows中,已经是这样了。

在Windows中,这已经是 纤细的读者和作家锁 如果你使用一个普通的 std::mutex 或类似的东西,你通常会最终在引擎盖下使用这种机制。

"老 "文献(10-15年)仍然会警告你不要使用OS基元进行调度,但这已经严重过时了,并没有反映出OS方面的改进。以前每次上下文切换都有10ms以上的延迟,现在基本上已经降到几乎无法测量了。