5

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

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

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

或者不加锁会有什么后果?

4

4 回答 4

6

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

于 2013-07-26T02:51:20.527 回答
5

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

请参阅以下链接:

这个博客帖子的方式也会让你感兴趣。

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

于 2013-07-26T02:55:08.687 回答
1

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

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

简而言之:是的,您应该确保互斥。

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

于 2013-07-26T02:55:08.807 回答
1

不受保护的增量/减量不是线程安全的- 因此线程之间不是原子的。(虽然它可能是“原子的” wrt 实际的 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;
}

并修改上面的代码以使用x.counter而不是局部counter变量。

于 2013-07-26T03:00:27.497 回答