是C#中的原子++操作

本文关键字:操作 | 更新日期: 2023-09-27 18:24:55

我有多个线程通过"++"或"--"操作访问单个Int32变量。

我们在访问它之前需要锁定吗,如下所示?

    lock (idleAgentsLock)
    {
       idleAgents--;  //we should place it here.
    }

或者如果我不上锁会有什么后果?

是C#中的原子++操作

它不是"原子的",不是多线程意义上的。然而,至少从理论上讲,你的锁可以保护操作。如果有疑问,您当然可以使用Interlocked.Decrement

No++/-不是原子操作,但是对整数和其他基元时间的读取和写入被视为原子操作。

请参阅以下链接:

  • 访问C#中的变量是原子操作吗
  • ++运算符线程安全吗
  • C#中哪些操作是原子操作

此外,这篇博客文章的方式对你有一些兴趣。

我建议Interlocked类中的Interlocked.Increment用于++/-运算符的原子实现。

这取决于机器体系结构。一般来说,不,编译器可能会生成一条加载指令,增加/减少值,然后存储它。因此,其他线程可能确实会在这些操作之间读取值。

大多数CPU指令集都有一个特殊的原子测试&为此设置指令。假设您不想在C#代码中嵌入汇编指令,那么下一个最好的方法是使用互斥,类似于您所展示的。该机制的实现最终使用原子指令来实现互斥(或它使用的任何东西)。

简而言之:是的,你应该确保相互排斥。

除了这个答案的范围之外,还有其他管理共享数据的技术,这些技术可能合适,也可能不合适,这取决于您的情况的域逻辑。

无保护的增量/减量不是线程安全的,因此不是线程之间的原子。(尽管它可能是"原子"的,但对于实际的IL/ML变换1。)

这个LINQPad示例代码显示了不可预测的结果:

void Main()
{
    int nWorkers = 10;
    int nLoad = 200000;
    int counter = nWorkers * nLoad;
    List<Thread> threads = new List<Thread>();
    for (var i = 0; i < nWorkers; i++) {
        var th = new Thread((_) => {
            for (var j = 0; j < nLoad; j++) {
                counter--; // bad
            }
        });
        th.Start();
        threads.Add(th);
    }
    foreach (var w in threads) {
        w.Join();
    }
    counter.Dump();
}

请注意线程之间的可见性非常重要。除了原子性之外,同步还保证了这种可见性。

这个代码很容易修复,至少在有限的上下文中是这样。切换减量并观察结果:

counter--;                           // bad
Interlocked.Decrement(ref counter);  // good
lock (threads) { counter--; }        // good

1即使使用volatile变量,结果仍然是不可预测的。这似乎表明(至少在这里,当我刚刚运行它时)它也是而不是原子运算符,因为竞争线程的读/操作/写是交错的。要查看可见性问题消除后的行为仍然不正确(是吗?),请添加

class x {
    public static volatile int counter;
}

并将上述代码修改为使用CCD_ 3而不是本地CCD_。