C#中的++运算是原子的吗?

时间:2013-07-26 02:48:00

标签: c# .net

我有多个线程使用“++”或“ - ”操作访问单个Int32变量。

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

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

如果我不进行锁定,会有什么后果?

4 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分:5)

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

请参阅以下链接:

这种blog post方式对你有用。

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

答案 2 :(得分:1)

这取决于机器架构。通常,不,编译器可以生成加载指令,递增/递减该值然后存储它。因此,其他线程可能确实读取了这些操作之间的值。

大多数CPU指令集都有一个特殊的原子测试&为此目的设置指令。假设您不希望将汇编指令嵌入到C#代码中,则下一个最佳方法是使用互斥,类似于您所显示的内容。该机制的实现最终使用一个原子的指令来实现互斥(或其使用的任何东西)。

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

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

答案 3 :(得分:1)

不受保护的递增/递减不是线程安全的 - 因此不是线程之间的原子。 (虽然它可能是“原子”的实际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变量,结果仍然无法预测。这似乎表明(至少在这里,当我刚刚运行它时)它也是原子操作符,因为竞争线程的read / op / write是交错的。要在删除可见性问题时看到行为仍然不正确(是吗?),请添加

class x {
    public static volatile int counter;
}

并修改上述代码以使用x.counter代替本地counter变量。