由于Monitor.Wait的暂时放弃和Monitor.Pulse的开门机制,我们可以用Monitor来实现更丰富的同步机制,比如一个事件机(ManualResetEvent):
- C# code
class MyManualEvent
{
private object lockObj = new object();
private bool hasSet = false;
public void Set()
{
lock (lockObj)
{
hasSet = true;
Monitor.PulseAll(lockObj);
}
}
public void WaitOne()
{
lock (lockObj)
{
while (!hasSet)
{
Monitor.Wait(lockObj);
}
}
}
}
class Program
{
static MyManualEvent myManualEvent = new MyManualEvent();
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(WorkerThread, "A");
ThreadPool.QueueUserWorkItem(WorkerThread, "B");
Console.WriteLine("Press enter to signal the green light");
Console.ReadLine();
myManualEvent.Set();
ThreadPool.QueueUserWorkItem(WorkerThread, "C");
Console.ReadLine();
}
static void WorkerThread(object state)
{
myManualEvent.WaitOne();
Console.WriteLine("Thread {0} got the green light...", state);
}
}
我们看到了该玩具MyManualEvent实现了类库中的ManulaResetEvent的功能,但却更加的轻便 -
类库的ManulaResetEvent使用了操作系统内核事件机制,负担比较大(不算竞态时间,ManulaResetEvent是微秒级,而lock是几十纳秒级)。
例子的WaitOne中先在lock的保护下判断是否信号绿灯,如果不是则进入等待。因此可以有多个线程(比如例子中的AB)在等待队列中排队。
当调用Set的时候,在lock的保护下信号转绿,并使用PulseAll开门放狗,将所有排在等待队列中的线程放入就绪队列,A或B(比如A)于是可以重新获得同步对象,从Monitor.Wait退出,并随即退出lock区块,WaitOne返回。随后B或A(比如B)重复相同故事,并从WaitOne返回。
线程C在myManualEvent.Set()后才执行,它在WaitOne中确信信号灯早已转绿,于是可以立刻返回并得以执行随后的命令。
该玩具MyManualEvent可以用在需要等待初始化的场合,比如多个工作线程都必须等到初始化完成后,接到OK信号后才能开工。该玩具MyManualEvent比起ManulaResetEvent有很多局限,比如不能跨进程使用,但它演示了通过基本的Monitor命令组合,达到事件机的作用。
现在是回答朋友们的疑问的时候了:
Q: Lock关键字不是有获取锁、释放锁的功能... 为什么还需要执行Pulse?
A:
因为Wait和Pulse另有用途。
Q: 用lock
就不要用monitor了(?)
A:
lock只是Monitor.Enter和Monitor.Exit,用Monitor的方法,不仅能用Wait,还可以用带超时的Monitor.Enter重载。
Q: Monitor.Wait完全没必要 (?)
A:
Wait和Pulse另有用途。
Q:
什么Pulse和Wait方法必须从同步的代码块内调用?
A:
因为Wait的本意就是“[暂时]释放对象上的锁并阻止当前线程,直到它重新获取该锁”,没有获得就谈不到释放。
我们知道lock实际上一个语法糖糖,C#编译器实际上把他展开为Monitor.Enter和Monitor.Exit,即:
- C# code
lock(lockObj)
{
//...
}
////相当于(.Net4以前):
Monitor.Enter(lockObj);
try
{
//...
}
finally
{
Monitor.Exit(lockObj);
}
但是,这种实现逻辑至少理论上有一个错误:当Monitor.Enter(lockObj);刚刚完成,还没有进入try区的时候,有可能从其他线程发出了Thread.Abort等命令,使得该线程没有机会进入try...finally。也就是说lockObj没有办法得到释放,有可能造成程序死锁。这也是Thread.Abort一般被认为是邪恶的原因之一。
DotNet4开始,增加了Monitor.Enter(object,ref
bool)重载。而C#编译器会把lock展开为更安全的Monitor.Enter(object,ref bool)和Monitor.Exit:
- C# code
lock(lockObj)
{
//...
}
////相当于(DotNet 4):
bool lockTaken = false;
try
{
Monitor.Enter(lockObj,ref lockTaken);
//
}
finally
{
if (lockTaken) Monitor.Exit(lockObj);
}
现在Monitor.TryEnter在try的保护下,“加锁”成功意味着“放锁”将得到finally的保护。
注释和引用:Monitor.Wait 方法
http://msdn.microsoft.com/zh-cn/library/79fkfcw1.aspx
Monitor.TryEnter
方法http://msdn.microsoft.com/zh-cn/library/dd289679.aspx
请问,多线程Monitor类http://topic.csdn.net/u/20111206/15/744c70de-49dc-4694-a09e-180438d7f8f0.html
请问,这个关于多线程的代码不懂http://topic.csdn.net/u/20111208/23/64671dd4-7fdc-4d76-b3b9-1fd18087e6e0.html