Parallel.For不正确处理锁定

时间:2012-02-18 03:50:26

标签: c# multithreading thread-safety

我做了以下测试:

private static object threadLocker = new object();

private static long threadStaticVar;
public static long ThreadStaticVar
{
    get
    {
        lock (threadLocker)
        {
            return threadStaticVar;
        }
    }
    set
    {
        lock (threadLocker)
        {
            threadStaticVar = value;
        }
    }
}

Parallel.For(0, 20000, (x) =>
{
    //lock (threadLocker) // works with this lock
    //{
        ThreadStaticVar++;
    //}
});

这个Parallel.For调用将值从0传递到19999的方法。因此它将执行20k次。

如果我不使用ThreadStaticVar++;包裹lock,即使它getset有锁定,结果也不会是{{1} }}。如果我删除评论栏并将其锁定在20000内,则会获得正确的值。

我的问题是:它是如何运作的?为什么.Forget上的锁不起作用?为什么它仅适用于我的set

3 个答案:

答案 0 :(得分:5)

++运算符不是原子增量。将调用get,然后调用set,并且这些调用可以在不同的线程之间进行交错,因为锁定仅针对每个单独的操作。可以这样想:

lock {tmp = var}
lock {var = tmp+1}

这些锁现在看起来不那么有效,是吗?

答案 1 :(得分:2)

在您的示例中,ThreadStaricVar++不是原子操作

更准确地说,++不是原子操作,因为它会锁定你的getter,然后递增值,然后锁定你的setter来设置值。在这两者之间可能发生任何事情:)

为了正确地做到这一点,我建议使用面向对象的编程而不是这个程序代码。只需在对象中实现Increment()方法,并使其负责锁定并在此方法中执行++。在您的并行循环中,您只需命令您的对象该做什么,现在该对象有责任实现它并找出如何执行它。

所以你只需在的Increment()方法中实现锁,并且在外面的任何地方都没有问题(实际上,消费者不应该知道,甚至不应该考虑这些问题)。

答案 2 :(得分:1)

您可以重命名threadStaticVar并将其公开。然后,使用Interlocked.Increment

但是,也要考虑并行是否合适。即使真正的代码更复杂,与锁定并行运行可能不是您的最佳选择。