使用 Monitor.Enter 锁定变量增量
本文关键字:变量 锁定 Monitor Enter 使用 | 更新日期: 2023-09-27 18:30:45
在下面的代码示例中:
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();
}
}
为什么即使我在监视器中使用锁定,Main 函数中的 while 循环也不会中断?如果我在线程中添加 Thread.Sleep(1) 而一切都按预期工作,甚至没有监视器......
监视器类是否发生得太快,没有足够的时间锁定?
注意:应使用 != 运算符。我知道我可以将其设置为<并解决问题。我试图实现的是看到它与监视器类一起工作,而不是在没有它的情况下工作。不幸的是,它不能双向工作。谢谢>
带有 while 的第一个线程可能会连续调度两次(即监视器可能不公平。
请参阅此相关问题:lock() 保证按请求的顺序获得吗?
假设您有 1 个可用的 CPU。这就是执行的样子
T1 [睡眠][增量][睡眠][增量][睡眠][增量][睡眠]T2 --[L][CK][UL][L][CK][UL][L][CK][UL][L][CK][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米利秒。
输出完全支持这一点,在我的机器上是
000...562835628356283...699482699482699482...
Monitor
类(或lock
关键字)用于进入和退出关键部分。关键部分是一个代码块,保证相对于由同一对象引用(要Monitor.Enter
的参数)定义的任何其他关键部分串行执行。换句话说,执行由同一对象引用定义的关键部分的两个或多个线程必须以阻止它们同时发生的方式执行此操作。但是,不能保证线程会以任何特定顺序执行此操作。
例如,如果我们A
代码的两个关键部分块进行标记,并将两个线程B
为 T1
和 T2
则以下任何一项都是执行序列的有效可视化表示形式。
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)
实际上是一种特殊情况,它强制调用线程将其时间片让给任何处理器的另一个线程。我不清楚你把这个调用放在你的程序中的什么位置,所以我不能过多地评论为什么它提供了所需的行为。但是,我可以说这主要是偶然的。
另外,我必须指出,您在此程序中有一个非常严重的错误。当您通过break
跳出while
循环时,您将绕过Monitor.Exit
调用,这将使锁处于获取状态。使用 lock
关键字要好得多,因为它会将Monitor.Enter
和Monitor.Exit
包装到一个try-finally
块中,从而保证始终释放锁。
因为 CPU 块通常为 40 毫秒。在此时间范围内,线程设法执行大量增量。线程退出监视器并立即获得上下文切换的情况并非如此。