平行.因为没有正确处理锁

本文关键字:正确处理 因为 平行 | 更新日期: 2023-09-27 18:24:58

我已经完成了以下测试:

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的值的方法。因此,它将执行2万次。

如果我不用lock包装ThreadStaticVar++;,即使它的getset上有锁,结果也不会是20000。如果我删除注释栏并将其锁定在.For中,它将获得正确的值。

我的问题是:它是如何工作的?为什么getset上的锁不起作用?为什么它只在我的For中工作?

平行.因为没有正确处理锁

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

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

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

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

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

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

因此,您只需在Increment()方法中实现锁,在外部任何地方都没有问题(实际上,消费者不应该知道,甚至不应该考虑这些问题)。

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

然而,也要考虑是否适合使用类似的。即使真正的代码更复杂,与锁定并行运行也可能不是最好的选择。