1

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

可以做些什么呢?

4

1 回答 1

1

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

然后是菠菜本身的成本。您正在消耗您的内存交易预算,而该预算实际上是在处理器内核之间共享的。实际上,这可能会导致关键部分中的操作减慢。

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


天真的解决方案是在自旋周期之间使用纳米睡眠,以至少降低锁本身的性能消耗。但这也很糟糕,因为核心仍然被阻塞,没有做任何真正的工作。

尝试在自旋锁中屈服?只是变得更慢,最终得到的最小延迟与操作系统的调度速率成正比。以 1 毫秒(Windows 实时模式,当任何进程请求时激活)、5 毫秒(Linux 默认 200Hz 调度程序)、10 毫秒(Windows 默认模式)的速率,这是引入执行的巨大延迟。而且,如果您碰巧再次碰到了关键部分,那将是一种浪费,因为您现在添加了上下文切换的开销而没有任何收益。


最终,对关键部分使用操作系统原语。常见的方法是使用原子操作来探测是否发生了任何争用,当发生争用时,才涉及操作系统。

无论哪种方式,下面的操作系统都有更好的方法来解决争用,主要是等待列表的形式。这意味着在信号量上等待的线程仅在允许它们恢复时才等待,并保证持有相应的锁。当离开争用区域时,拥有锁的线程通过轻量级方式检查是否存在任何争用,并且仅当存在争用时才通知操作系统恢复对其他线程的操作。


并不是说您实际上应该重新发明轮子……

在 Windows 中,这就是Slim Reader/Writer Locks的实现方式。如果您使用普通的std::mutex或类似的,您通常已经在引擎盖下使用了这种机制。

“旧”文献(10-15 年)仍然会警告您不要使用 OS 原语进行调度,但这已经严重过时,并没有反映在 OS 方面所做的改进。过去每次上下文切换的延迟为 10 毫秒以上,如今基本上已经无法测量。

于 2020-06-01T10:29:11.987 回答