4

在以下代码示例中:

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();
    }
}

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

Monitor 类没有足够的时间来锁定是不是发生得太快了?

注意: != 运算符是有意的。我知道我可以将其设置为 < 并解决问题。我试图实现的是看到它与 Monitor 类一起工作,而没有它就无法工作。不幸的是,这两种方式都不起作用。谢谢

4

4 回答 4

3

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

请参阅此相关问题:是否 lock() 保证按请求的顺序获得?

于 2013-09-09T14:06:25.200 回答
3

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

T1 [睡眠][增加][睡眠][增加][睡眠][增加][睡眠]
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]...

在哪里:

T1is th thread
T2is main thread
[L][CK][UL]is lock, check, unlock - 主线程的工作量
CPU1是 CPU 的任务调度

注意 short[T1]是对Thread.Sleep. 这会导致当前线程立即让出控制权。此线程将不会被安排执行时间大于或等于指定的毫秒参数。

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

重要提示: T1不会执行单个增量然后切换到另一个线程。这就是问题所在。它将进行多次迭代,直到当前线程执行量到期。平均而言,您可以考虑执行 quant ~ 10-30 毫秒。

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

0
0
0
...
56283
56283
56283
...
699482
699482
699482
...
于 2013-09-09T14:37:02.583 回答
2

因为 CPU 块通常是 40 毫秒。在此时间范围内,线程设法进行大量增量。不是线程退出监视器并立即获得上下文切换的情况。

于 2013-09-09T14:09:28.770 回答
2

Monitor(或lock关键字)用于进入和退出临界区。临界区是保证相对于由同一对象引用(to 的参数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.Enterand包装Monitor.Exit到一个try-finally块中,这将保证锁将始终被释放。

于 2013-09-09T15:10:55.893 回答