【问题标题】:Why "lock" block not work?为什么“锁定”块不起作用?
【发布时间】:2011-09-08 10:09:03
【问题描述】:
public class ThreadInteroperateWithLock
{
    private int m_count;
    private object m_synLock;
    public ThreadInteroperateWithLock() 
    {
        m_count = 0;
        m_synLock = new object();
    }

    public int Count { get { return m_count; } }

    public void Add() 
    {
        //just simulate some work
        int temp=0;
        for (int i = 0; i < 10000; i++)
        {
            temp++;
        }

        //really job
        lock (m_synLock)
        {
            m_count++;
        }
    }
}

此代码在控制台应用程序中:

ThreadInteroperateWithLock ope = new ThreadInteroperateWithLock();
Thread[] threadArray = new Thread[100];
for (int i = 0; i < 100; i++)
{
    Thread thread = new Thread(new ThreadStart(ope.Add));
    thread.IsBackground = false;
    threadArray[i] = thread;
}
for (int i = 0; i < 100; i++)
{
    threadArray[i].Start();
}
Console.WriteLine(ope.Count);
Console.ReadKey();

无论lock{...} 块是否存在,它有时会打印“99”,有时会打印“100”。我的代码有问题吗?

【问题讨论】:

    标签: c# thread-safety


    【解决方案1】:

    这里的问题是您正在启动线程,并且在您调用将输出写入控制台时它们还没有完全完成。

    循环从 i 线程开始,我们将把这些想象成蜜蜂将收集作为工作,并不是所有的蜜蜂都去同一个食物来源,所以有些人比其他人需要更长的时间才能返回;然后,回到蜂巢,我们突然说:“嘿,蜜蜂,我需要人数!”,“……1、2、3……,只有三个?”不,一些 i-3 还在外面四处游荡!

    因此,我们的想法是,我们必须有一个工作完成时间的指示器,或者某种归巢信号,以让所有蜜蜂回到蜂巢中进行人数统计。这可以通过Join 或手动状态检查来完成(这基本上让您坚持到最后一个旅居者返回。)

    【讨论】:

    • 非常感谢!我只关注子线程和其他子线程之间的同步,忘记了主线程。这是一个多么愚蠢的问题。
    【解决方案2】:

    您不是在等待线程完成。在您输出计数之前,有多少人完成了他们的工作,这完全取决于运气。

    添加

            for (int i = 0; i < 100; i++)
            {
                threadArray[i].Join();
    
            }
    

    WriteLine 之前,你总是得到100

    【讨论】:

      【解决方案3】:

      您应该使用锁来保护您的 getter public int Count { get { return m_count; } },否则线程 B 可能正在读取该值,而另一个线程 A 正在更新您的 Add 方法中的计数,这可能会导致您获得不一致的数据视图.

      【讨论】:

        【解决方案4】:

        您对Count 的调用发生在不是确定性的。它可能发生在任何线程完成之前,或者在所有线程完成之后,甚至在中间的某个地方。如果你想等到所有线程都完成,你应该加入它们。

        【讨论】:

          【解决方案5】:

          这是一个经典的race condition,我认为你很幸运能在你的Console.WriteLine 被调用时接近 100。

          for (int i = 0; i < 100; i++)
          {
              threadArray[i].Start();
          }
          //absolutely no guarantee that **ANY** threads have completed before next line
          Console.WriteLine(ope.Count);
          

          考虑使用CountdownEvent 或类似的来做一点同步。

          【讨论】:

            【解决方案6】:

            不确定这是否是您正在寻找的答案,但要以线程安全的方式增加计数器,您应该使用 Interlocked.Increment docs

            【讨论】:

            • 是的,它应该使用 Interlocked.Incremet 或创建一个带有 Intenger 作为字段的新对象,因为 Lock 仅适用于 Object,不适用于 Primitive 数据类型。
            【解决方案7】:

            当您打印计数时,您不知道线程是否已完成。代码中的“正确”输出可以是 0 到 100 之间的任何数字。

            为了确保得到 100 作为输出,在所有线程完成之前,您不能得到 Count 的值。您可以通过在所有线程上调用 Thread.Join 来做到这一点,或者(甚至更好)使用 Parallel.For 之类的东西来启动线程:

            Parallel.For(0, 100, idx => ope.Add);
            // When we reach this line, we know all threads have completed so we can 
            // safely get the count
            var count = ope.Count;
            

            【讨论】:

              猜你喜欢
              • 2015-06-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-04-20
              • 2021-12-20
              • 2019-12-29
              • 1970-01-01
              相关资源
              最近更新 更多