C# 中运算符 + 或 ++ 的内存分配

Memory assignment in C# for operator + or ++

如果之前问过这个问题,请原谅我,因为我找不到好的关键字来搜索它。因此,如果您对以下问题的关键字有任何建议,请告诉我。

所以问题是:假设我有一个 class

class Test
{
   public int testProp;
}

当我做类似

的事情时
var testInstance = new Test();
testInstance.testProp += 10;

testProp的地址还会和+操作前一样吗?当我尝试使用 Visual Studio 反汇编 window 时,我发现它似乎将新值复制到相同的地址(请在这里更正我,因为我不熟悉汇编语言)。

我问是因为在多线程访问的情况下 属性 并同时增加它的值

var list = new List<Task>();
for (var z = 0; z < 2000; z++)
{
   list.Add(Task.Run(() =>
      {
         testInstance.testProp+=10;
      }));
}

有时它会给我 20000,但有时只有 19990 或更少,所以我很困惑如果 testProp 的地址改变了,如果没有改变地址的值是如何读取的在这种情况下总和小于 20000?

内存地址不变。但是,+= 运算符不是原子的。这意味着,它不会在单个操作中读取值、增加值和替换原始值。想象一下递增的顺序是这样的:

READ value
INC value
WRITE value

对于多线程,这些可以混用:

THREAD A READ value
THREAD B READ value  # same value read
THREAD B INC value
THREAD B WRITE value
THREAD A INC value
THREAD A WRITE value   # does not take into account value written by B

所以,当你认为它应该递增两次时,实际上它只递增了一次。由于线程 B 中完成的所有工作都被线程 A 覆盖。

+= 运算符与 int 属性或字段一起使用时,会执行多项操作:

  • 得到
  • 添加
  • 设置

这意味着它不是 atomic,竞争条件会导致更改丢失。如果你想要一个原子增量,你可以使用:

Interlocked.Add(ref testInstance.testProp, 10);

这有更多的开销,但它会给你正确的结果。如果您不能直接访问该字段,则可能必须通过 lock.

进行同步

多线程同步与写入哪个内存地址无关。要执行多线程安全原子操作,请使用 Interlocked class,即 Interlocked.Increment.