我有多个线程使用“++”或“ - ”操作访问单个Int32变量。
我们是否需要在访问之前锁定,如下所示?
lock (idleAgentsLock)
{
idleAgents--; //we should place it here.
}
如果我不进行锁定,会有什么后果?
答案 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
变量。