【问题标题】:Memory assignment in C# for operator + or ++C# 中为运算符 + 或 ++ 分配内存
【发布时间】:2020-10-13 21:48:31
【问题描述】:

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

所以问题是:假设我有一堂课

class Test
{
   public int testProp;
}

当我做类似的事情时

var testInstance = new Test();
testInstance.testProp += 10;

testProp 的地址是否仍与 + 操作之前相同?当我尝试使用 Visual Studio 反汇编窗口时,我发现它似乎将新值复制到相同的地址(请在此处纠正我,因为我不熟悉汇编语言)。

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

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

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

【问题讨论】:

  • 变量的地址没有改变。存储在该地址的值会发生变化。
  • 好吧 testProp 是 field 不是属性,差别很大。实际上将field 公开是一个大罪。解决问题的最简单方法是将public int testProp 更改为public volatile int testProp
  • @Legacy Code 是的,我知道,这只是我尝试过的一些快速示例代码。
  • @LegacyCode 只是在您声明此问题的所有地方都明确说明:不,volatile 不能解决此问题
  • @MarcGravell 我显然无法编辑我的评论以删除它...

标签: c# .net memory-management heap-memory


【解决方案1】:

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

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 覆盖。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      • 得到
      • 添加
      • 设置

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

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

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

      【讨论】:

      • 感谢您的出色回答。我搜索了原子操作,学到了很多东西。
      • @LegacyCode 使用 volatile 确实 not 使事情变得原子 - 你可以得到完全相同的问题; volatile(作为它实际上所做的事情的副作用)只是防止了从寄存器而不是实际字段重复读取字段的情况;使用 volatile 时,您仍然会丢失更新
      • @LegacyCode no,不需要使用lock + volatile;锁明确地是一个内存屏障(全栅栏),它比 volatile 实现的更多;如果您使用的是 lock + volatile,那么您可能不了解 volatile(公平地说,大多数人不了解;除了每个人都认为的有用的副作用之外,我很难清楚地表达它)。不,它也不能解决循环问题——因为错误值不一定来自寄存器(事实上,我敢打赌它们不是)——这看起来像简单的竞态条件,与volatile 无关
      • @DanLe 有趣的是:整个“值未缓存”实际上是 volatile 的副作用,而不是意图; intent 完全是关于 CPU 指令重新排序 :)
      • 很高兴知道...我一直认为未缓存的部分首先是易失性的原因。
      猜你喜欢
      • 2014-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-18
      • 1970-01-01
      • 2020-04-06
      • 1970-01-01
      • 2021-09-05
      相关资源
      最近更新 更多