使用Monitor.Enter锁定变量增量

时间:2013-09-09 14:02:20

标签: c# multithreading

在以下代码示例中:

class Program
{
    private static int counter = 0;
    public static object lockRef = new object();

    static void Main(string[] args)
    {
        var th = new Thread(new ThreadStart(() => {
            Thread.Sleep(1000);
            while (true)
            {
                Monitor.Enter(Program.lockRef);
                ++Program.counter;
                Monitor.Exit(Program.lockRef);
            }
        }));
        th.Start();

        while (true)
        {
            Monitor.Enter(Program.lockRef);
            if (Program.counter != 100)
            {
                Console.WriteLine(Program.counter);
            }
            else
            {
                break;
            }
            Monitor.Exit(Program.lockRef);
        }
        Console.Read();
    }
}

为什么即使我使用Lock with Monitor,Main函数内的while循环也不会中断? 如果我在Thread中添加Thread.Sleep(1),而一切都按预期工作,甚至没有Monitor ...

它只是发生得太快以至于Monitor类没有足够的时间来锁定吗?

注意: 打算使用!=运算符。我知道我可以把它设置为<并解决问题。我试图实现的是看到它与Monitor类一起使用而不使用它。不幸的是,它无论如何都不起作用。 感谢

4 个答案:

答案 0 :(得分:3)

带有while的第一个线程可能会连续两次调度(即Monitor可能不公平。)

请参阅此相关问题:Does lock() guarantee acquired in order requested?

答案 1 :(得分:3)

假设您有1个CPU可用。这就是执行的样子

T1        [SLEEP][INCREMENT][SLEEP][INCREMENT][SLEEP][INCREMENT][SLEEP]
T2   --[L][CK][UL][L][CK][UL][L][CK][UL][L][CK][UL][L][CK][UL][L][CK][UL]
CPU1 [  T2  ][T1][  T2  ][  T1  ][  T2  ][T1][  T2  ][  T1  ][  T2  ][T1]...

其中:

T1 帖子
T2主题
[L][CK][UL]是锁定,检查,解锁 - 主线程的工作量 CPU1是CPU的任务调度

注意短[T1]是对Thread.Sleep的调用。这导致当前线程立即产生控制。该线程不会被安排执行大于或等于指定的毫秒参数的时间。

更长[ T1 ]while循环中的增量发生的地方。

重要提示: T1不会执行增量,然后切换到另一个线程。这就是问题所在。在当前线程执行量程到期之前,它将进行许多迭代。平均而言,你可以认为执行量约为10-30毫秒。

输出完全支持这一点,在我的机器上是

0
0
0
...
56283
56283
56283
...
699482
699482
699482
...

答案 2 :(得分:2)

因为CPU块通常为40ms。在此时间范围内,线程设法进行大量增量。线程不会退出监视器并立即进行上下文切换。

答案 3 :(得分:2)

Monitor类(或lock关键字)用于进入和退出关键部分。关键部分是一个代码块,保证相对于由同一对象引用(Monitor.Enter的参数)定义的任何其他临界区串行执行。换句话说,执行由同一对象引用定义的关键部分的两个或多个线程必须以防止它们同时发生的方式这样做。但是不能保证线程会以任何特定的顺序执行此操作。

例如,如果我们将代码AB的两个关键部分块标记为T1T2,则以下任何一个都有效可视化执行序列的表示。

T1: A A A . . . A . A A .
T2: . . . B B B . B . . B

T1: . A A . . A A
T2: B . . B B . .

T1: A A A A A A A .
T2: . . . . . . . B

T1: A . A . A . A . A .
T2: . B . B . B . B . B

可能的交错排列的域是无限的。我刚给你看了一个无限小的子集。只是最后的排列将导致您的程序以您期望的方式工作。当然,这种排列极不可能无用,你引入其他机制来迫使它发生。

您提到Thread.Sleep(1)改变了您的计划的行为。这是因为它影响操作系统如何安排线程的执行。 Thread.Sleep(1)实际上是一种特殊情况,它强制调用线程将其时间片产生到任何处理器的另一个线程。我不清楚你把这个电话放在你的程序中的哪个位置,所以我不能过多地评论它为什么会提供所需的行为。但是,我可以说这是偶然的。

另外,我必须指出你在这个程序中有一个非常重要的错误。当您通过while跳出break循环时,您将绕过Monitor.Exit调用,这会使锁定处于获取状态。最好使用lock关键字,因为它会将Monitor.EnterMonitor.Exit包装到try-finally块中,以保证锁定始终被释放。