1

关于内存屏障的使用,我有一些不明白的地方,我希望得到澄清。

所以,假设我们有一个 Treibers 堆栈,但是我们使用的是 SMR,所以没有与每个指针关联的计数器 - 我们必须在我们的原子操作中正确获取指针(这与 ABA 无关 - 我们使用的是 SMR,处理 ABA,这不是问题的一部分)。

现在,假设我们正在使用 Intel (x86/x64),因此每个 CAS 都带有完整的内存屏障。我认为CASing 时会发生什么情况是缓存行被锁定,发出读屏障,这会清除无效队列,缓存行因此加载该缓存行的最新版本,发生比较,然后写屏障是发出,刷新存储缓冲区,最后我们释放缓存行锁。

所以,我们有下面的pop代码;

BARRIER_PROCESSOR_READ;

original_top = stack_state->top;

do
{
  if( original_top == NULL )
    return( 0 );

  copy_of_original_top = original_top;

  original_top = compare_and_swap( &stack_state->top, original_top->next, original_top );
}
while( copy_of_original_top != original_top );

*user_data = original_top->user_data;

所以,我们首先发出一个读屏障——这确保我们刷新我们的无效队列。但是这样做和读取 state_state->top 之间存在差距。在清除无效队列和读取 state_stack->top 之间,任何事情都可能发生。核心可以服务中断,发生总线争用并且非常慢,您可以命名它 - 可以重新加载无效缓存行(并由另一个处理器重新无效)。基本上 - 无效队列可以重新填充。这意味着我们实际上不能相信 original_top 的值;我们可能正在读取一个实际上是错误的本地缓存行(我们还没有使它失效)并且这样做,错误地认为它的值为 NULL 并返回 0。

所以基本上,我看不出阅读障碍有什么帮助,因为在障碍之后但在您希望执行的实际阅读之前,任何事情仍然可能发生。

我在这里想念什么?

4

2 回答 2

1

我不完全理解你的问题,但我强烈怀疑你遗漏了一个细节。

内存防护用于保证更改的可见性,而不是同步进程。单独的防护不会锁定对数据的访问。

另一方面,原子操作和锁(如互斥锁或临界区或信号量或任何其他同步原语)都将保证只有一个线程访问给定的内存区域(假设所有访问都被编码为在您“拥有”时发生这样的锁或原子)。但它们不能保证有序的可见性。

如果需要,则同时需要独占访问防护(注意:防护通常已经作为高级同步原语(如互斥锁)的一部分实现,因此如果您使用它们,则不必显式地担心防护)。

于 2012-09-21T11:50:24.963 回答
0

我不完全确定我理解你的问题,但是,在发出读取障碍之后,任何后续读取肯定会在障碍之前发生的读取之后排序。根据 BARRIER_PROCESS_READ 的定义方式,它还可能强制后续读取从共享内存而不是特定于处理器的高速缓存行中提取数据,其效果是在其他处理器上执行的写入将是可见的(假设这些写入后面跟着一个适当的写屏障!)。

在存在中断的情况下,这些事情仍然正确。即使缓存行在读屏障之后从中断处理程序中被填充,那么从这些缓存行中读取仍然会给你一个相对于读屏障语义有效的值。

我怀疑在您提供的代码示例中,读取屏障的目的实际上是使其他处理器的写入可见,以确保下一行original_top = stack_state->top; - 检索新值而不是读取后已在本地缓存的值这发生在屏障之前。如果中断处理程序读取相同的地址,则此约束仍然成立。读取的值不会是“新鲜的”,但至少不会是缓存了无限时间的值。

于 2012-09-21T11:47:49.177 回答