0

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

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 完全相同。我最好的猜测是这是由于缓存一致性延迟:我使用的是 Intel i7 Ivy Bridge Machine 和这种微架构解决了 L3 的缓存一致性问题。VTune 还告诉该while (...)指令正在消耗所有前端带宽,但为什么呢?

为了明确问题:为什么while(...)和更新标志指令需要这么多执行时间?为什么while(...)指令会使前端带宽饱和?

4

2 回答 2

1

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

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

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

现在这取决于硬件中的实际实现,但如果线路在实际进行更改之前被窥探,第一个核心将不得不再次尝试获得它的所有权。在某些情况下,我想这可能需要多次尝试。

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

于 2013-10-26T03:34:50.883 回答
0

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

当您忙于等待时,线程调度算法将无法知道您的循环正在等待另一个线程,因此它们必须分配时间,就好像您的线程正在做有用的工作一样。并且确实需要处理器时间来检查该变量,一遍又一遍,一遍,一遍,一遍,一遍……直到它最终被另一个线程“解锁”。与此同时,你的另一个线程将一次又一次地被你的忙等待线程抢占,完全没有目的。

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

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

当您使用信号量或互斥体时,调度程序知道在释放信号量或互斥体之前永远不会调度该线程。因此,您的线程将永远不会从真正工作的线程中抽出时间。

于 2013-10-26T02:46:30.700 回答