繁忙等待现代处理器的利弊

时间:2013-10-26 02:32:36

标签: c++ multithreading parallel-processing x86 computer-architecture

我正忙着等待同步对关键区域的访问,如下所示:

while (p1_flag != T_ID);

/* begin: critical section */
for (int i=0; i<N; i++) {
 ... 
}
/* end: critical section */

p1_flag++;

p1_flag是一个全局volatile变量,由另一个并发线程更新。事实上,我在一个循环中有两个关键部分,我有两个线程(都执行相同的循环),这些线程通过执行这些关键区域。例如,关键区域被命名为A和B.

Thread 1     Thread 2
   A        
   B            A
   A            B
   B            A
   A            B
   B            A
                B

并行代码执行速度比串行代码快,但不如我预期的那么多。使用VTune Amplifier分析并行程序我注意到在同步指令中花费了大量时间,即while(...)和标志更新。我不确定为什么我在这些“指令”上看到如此大的开销,因为区域A与区域B完全相同。我最好的猜测是这是由于缓存一致性延迟:我正在使用英特尔i7 Ivy Bridge Machine和这个微架构解决了L3上的缓存一致性。 VTune还告诉while (...)指令消耗所有前端带宽,但为什么呢?

使问题清楚:为什么while(...)和更新标志指令需要花费如此多的执行时间?为什么while(...)指令会使前端带宽饱和?

2 个答案:

答案 0 :(得分:1)

您支付的开销很可能是因为在核心缓存之间来回传递同步变量。

缓存一致性决定了当您修改缓存行(p1_flag ++)时,您需要对其拥有所有权。这意味着它将使其他核心中存在的任何副本无效,等待它将该其他核心所做的任何更改写回共享高速缓存级别。然后,它将为M状态的请求核心提供该行并执行修改。

然而,另一个核心将会不断读取这一行,读取将窥探第一个核心并询问它是否具有该行的副本。由于第一个核心持有该行的M副本,因此它将被写回共享缓存,核心将失去所有权。

现在这取决于HW中的实际实现,但是如果在实际进行更改之前已经窥探了该行,则第一个核心必须再次尝试获得它的所有权。在某些情况下,我想这可能需要多次尝试。

如果您使用忙等待,则至少应该在其中使用一些暂停 :_mm_pause内向,或仅__asm("pause")。这既可以使另一个线程有机会获得锁定并使您免于等待,也可以减少繁忙等待时的CPU工作量(无序CPU将使用此忙碌等待的并行实例填充所有管道,消耗大量的能量 - 暂停会将其序列化,因此在任何给定时间只能运行一次迭代 - 消耗更少,效果相同。)

答案 1 :(得分:0)

繁忙等待在多线程应用程序中几乎不是一个好主意。

当你忙等待时,线程调度算法将无法知道你的循环正在等待另一个线程,因此它们必须分配时间,就像你的线程正在做有用的工作一样。并且确实需要处理器时间来检查该变量是否超过,结束,结束,结束,结束,结束......直到它最终被解锁并且#34;由另一个线程。与此同时,您的其他线程将被您的忙碌等待线程一次又一次地抢占,完全没有任何意义。

如果调度程序是基于优先级的调度程序,并且忙等待线程处于更高优先级,则这是更糟糕的问题。在这种情况下,优先级较低的线程永远不会抢占优先级较高的线程,因此会出现死锁情况。

您应该始终使用信号量或互斥对象或消息传递来同步线程。我从来没有见过忙碌等待是正确解决方案的情况。

当您使用信号量或互斥锁时,调度程序知道永远不会安排该线程,直到释放信号量或互斥锁。因此,你的线程永远不会花时间去做真正工作的线程。