3

这是我目前正在维护的一些代码的简化版本:

int SomeFunc() 
{
  const long lIndex = m_lCurrentIndex;
  int nSum = 0;
  nSum += m_someArray[lIndex];
  nSum += m_someArray[lIndex];
  return nSum;
}

lCurrentIndex 由另一个线程定期更新。问题是; 制作 m_CurrentIndex 的本地副本是否会确保对 m_someArray 的两个访问都使用相同的索引?

请注意,这是一个简化的示例;我正在考虑制作本地副本的概念,而不是此处显示的确切代码。我知道编译器会将值放入寄存器中,但这仍然是本地副本,而不是从 lCurrentIndex 读取两次。

谢谢!

编辑:初始分配是安全的,在我们的设置中都保证是 32 位的。Edit2:它们在 32 位边界上正确对齐(忘了那个)

4

7 回答 7

15

不,读取共享变量的本地初始化不一定是原子的。(例如,考虑在 8 位平台上需要什么代码)通常,编写线程安全代码的唯一方法是使用编译器和/或操作系统指定的原子操作,或者使用操作系统锁定功能。

于 2009-07-28T08:05:39.200 回答
6

制作 m_CurrentIndex 的本地副本是否会确保对 m_someArray 的两个访问都使用相同的索引?

在同一次执行中SomeFunc,是的,当然。局部整数变量 ( lIndex) 不会在函数中间神奇地改变它的值。

当然,以下情况也是正确的:(m_someArray[lIndex]相对于 的lIndex)的实际值可能会发生变化;m_someArray本身可能会改变;以及Neil 所说的 lIndex 初始值的有效性

于 2009-07-28T08:03:46.570 回答
3

是的,当前索引的副本将确保两个数组访问使用相同的索引。不过,这并不是我真正认为的“线程安全”。您需要关注对共享变量的并发访问。在我看来,对数组的访问也可能是一个潜在问题。

于 2009-07-28T08:04:32.417 回答
3

这应该是线程安全的(至少它会在我使用过的所有编译器/操作系统上)。但是,为了更加确定您可以声明m_lCurrentIndexvolatile. 然后编译器将知道它可能随时更改。

于 2009-07-28T08:57:11.563 回答
1

本地副本的概念

我正在考虑制作本地副本的概念,而不是此处显示的确切代码。

如果不了解更多细节,就无法回答这个问题。如果将 m_lCurrentIndex 的“本地复制”到 lIndex 是原子的,则归结为问题。

假设 x86 并假设 m_lCurrentIndex 是 DWORD 对齐的并假设 long 表示 DWORD(在大多数 x86 编译器上都是如此),那么是的,这是原子的。假设 x64 并假设 long 表示 DWORD 并且 m_lCurrentIndex 是 DWORD 对齐的,或者 long 表示 64b 字并且 m_lCurrentIndex 又是 64b 对齐的,是的,这是原子的。在其他平台上或没有对齐保证的情况下,副本可能需要两次或更多物理读取。

即使没有本地副本是原子的,您仍然可以使用 CAS 样式循环使其无锁和线程安全(乐观并假设您可以不加锁,在执行操作后检查一切是否正常,如果没有,回滚和再试一次),但它可能需要更多的工作,结果将是无锁的,但不是无等待的。

记忆屏障

请注意:一旦您向前迈出一步,您很可能会同时处理多个变量,或者处理充当指针或索引以访问其他共享变量的共享变量。到那时,事情将变得越来越复杂,因为从那时起,您需要考虑诸如读/写重新排序和内存屏障之类的事情。另请参阅如何编写无锁结构

于 2009-07-28T08:16:48.943 回答
1

这里要问的另一个问题是:从复制操作m_lCurrentIndexlIndex原子操作?如果不是这样,您最终可能会使用非常奇怪的值,这可能无济于事。:)

要点是:当您使用多个线程时,将无法解决某种锁定或同步问题。

于 2009-07-28T08:06:48.857 回答
0

是的,它将访问数组的相同元素。就像您正在将 m_lCurrentIndex 的值快照到局部变量中。因为局部变量有它自己的内存,所以无论你对 m_lCurrentIndex 做什么都不会影响局部变量。但是,请注意,由于不能保证赋值操作是原子的,因此您很可能会在 lIndex 中得到一个无效值。如果您从一个线程更新 m_lCurrentIndex 并同时尝试从另一个线程分配到 lIndex,则会发生这种情况。

于 2009-07-28T08:06:02.693 回答