Lock Is';t停止重复

本文关键字:Is Lock | 更新日期: 2023-09-27 18:27:17

我的代码的Lock()关键部分有问题。现在的情况是,我的输出包含重复项,我不能这样!下面你可以看到复制我的情况的代码。我真的很愚蠢,因为我的处境,对不起,如果我错过了一些非常愚蠢的东西。。。

private static object theLock = new object();
private static int currentNumber = 0;

。.

static void Main(string[] args)
{
    for (int i = 0; i < 30; i++)
        CreateAndRunWorker();
}

。.

private static void CreateAndRunWorker()
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(TheWorkToBeDone);
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(TheWorkAfterWork);
    worker.RunWorkerAsync();
}

。.

private static void TheWorkToBeDone(object sender, DoWorkEventArgs e)
{
    OutputNum();
}

。.

private static void TheWorkAfterWork(object sender, RunWorkerCompletedEventArgs e)
{
     CreateAndRunWorker();
}

。.

private static void OutputNum()
{
    lock (theLock)
    {
        currentNumber++;
        Console.WriteLine(currentNumber);
    }
}

上面是由一个Main()函数的25个后台工作人员调用的。锁和计数器被全局初始化。输出包含重复的数字。怎样

Lock Is';t停止重复

对于不相关内核上的本地缓存,您可能会遇到一些缓存效果。当前的问题是int本身没有以确保本地缓存保持最新的方式声明或更新。你可以用两种方式来解决这个问题:

修复您的实施

private static readonly object theLock = new object();
private static volatile int currentNumber = 0; // note the addition of "volatile"
private static void OutputNum()
{
    lock (theLock)
    {
        currentNumber++;
        Console.WriteLine(currentNumber);
    }
}

volatile关键字对于强制所有本地缓存的对该数字的引用读取相同内容非常重要。在微优化中,有些情况下,无序执行可能会导致某些芯片架构出现竞争状况。不过,仍然需要锁来保护不受增量竞争条件的影响。

readonly关键字对于防止锁对象在运行时被覆盖非常重要。一旦初始化,readonly锁对象的实例就永远无法更改。如果您确实更改了锁对象的实例,那么在很短的时间内,两个线程可以同时访问计数器变量。

解除锁定

如果你使用Interlocked.Increment函数,你可以在不使用锁的情况下获得你想要的效果。

private static volatile int currentNumber = 0;
private static void OutputNum()
{
    int localRef = Interlocked.Increment(ref currentNumber);
    Console.WriteLine(localRef);
}

这完全消除了锁,使解决方案保持更高的性能,因为锁争用较少,但保留了所有的安全性。

重要信息:使用Interlocked.Increment返回的值,以便将来在相同方法中工作,因为在Console.WriteLine()执行之前,其他线程访问相同代码并更改值不会改变这一点。

理解为什么这样做

  1. CLR在运行时执行优化并重新编译代码。volatile关键字确保优化不包括访问计数器。(https://msdn.microsoft.com/en-us/library/x13ttww7.aspx)
  2. 如果锁实例在运行时发生更改,那么您将有一个短暂的时间来获得竞争条件。一个线程锁定在旧对象上,而另一个线程则锁定在新对象上。始终声明您的锁对象,这样它们就不会更改。如果它是一个静态对象,那么static readonlyconst就是您想要的。如果它是对象内部的一个字段,用于保护一个实例,则将锁声明为readonlyreadonly关键字确保实例在包含作用域的生命周期内永远不会更改。(https://msdn.microsoft.com/en-us/library/acdd6hb7.aspx)
  3. CCD_ 11执行一个原子更改,保证在所有运行的内核中都是安全的。如果代码的关键部分只是增加一个计数器,那么这种方法可以减轻系统对重锁的需求。只需对方法的其余部分使用返回值,使其在该范围内保持不变。(https://msdn.microsoft.com/en-us/library/dd78zt0c(v=vs.110).aspx)

有关该主题的详细摘要,请参阅https://stackoverflow.com/a/154803/476048

出于学术目的,Java中也存在同样的问题和解决方案。API只是有点不同。