您的位置:首页 > 其它

Monitor vs Pulse

2008-06-25 19:52 295 查看
仅仅依据代码,结合msdn,谈经验,基础知识自行msdn or google,ok,let's go

先看一段最简单的

1: using System;
2: using System.Collections.Generic;
3: using System.Text;
4: using System.Threading;
5:
6: namespace MonitorTest
7: {
8:     class Program
9:     {
10:         static object _lock;
11:
12:         static void Main(string[] args)
13:         {
14:             _lock = new object();
15:             Thread.CurrentThread.Name = "main";
16:
17:             Log();
18:
19:             Thread t1 = new Thread(Work);
20:             t1.Name = "work";
21:             t1.IsBackground = true;
22:
23:             Thread t2 = new Thread(Notify);
24:             t2.Name = "notify";
25:             t2.IsBackground = true;
26:
27:
28:             t1.Start();
29:             //t2.Start();
30:
31:             t1.Join();
32:             //t2.Join();
33:
34:             Log();
35:         }
36:
37:         static void Log()
38:         {
39:             Console.WriteLine("thread: {0}, at: {1}, state: {2}",
40:                 Thread.CurrentThread.Name,
41:                 DateTime.Now,
42:                 Thread.CurrentThread.ThreadState);
43:         }
44:
45:         static void Work()
46:         {
47:             lock (_lock)
48:             {
49:                 Log();
50:                 Monitor.Wait(_lock);
51:                 Log();
52:             }
53:         }
54:
55:         static void Notify()
56:         {
57:             lock (_lock)
58:             {
59:                 Thread.Sleep(1000);
60:                 Monitor.Pulse(_lock);
61:                 Log();
62:             }
63:         }
64:     }
65: }

可以在console中看到2行输出





程序运行到这里,无论等上1年抑或2年,还是只会看到这2行输出,也就是说线程在这里发生死锁了,为什么呢?

从代码中可以看出此时除了primary线程之外,只启动了一个新的线程(work),在这个线程里有一行关键代码:

Monitor.Wait(_lock);

根据msdn描述:


The thread that currently owns the lock on the specified object invokes this method in order to release the object so that another thread can access it.


也就是说在lock(_lock)获取_lock对象上的锁之后(由于没有其他线程跟它抢占,Enter进入ready队列之后,就直接获取锁的使用权了),Wait这个方法反而放弃了锁的使用权,同时阻塞当前的work线程,同时也没有超时参数,因此work线程就直接休眠(进入WaitSleepJoin状态),同时在主线程中Join这个work线程时,也就一直不能返回了,所以....

现在把注释的29和32行启用,再运行,运行结果变成了





可以证明以下几个结论:

是Monitor.Pulse拯救了work这个线程,并且是在“精确”的1000毫秒之后
Monitor.Wait会释放锁的使用权 //不然Notify方法不会得到调用

同时,从运行结果中可以猜出程序运行流程



在做一个试验,将28行和29行对调位置,即:

t2.Start();
t1.Start();

运行结果变成了





程序运行到这里又死掉了.... 为什么呢?不是已经Pulse了吗? msdn中有这样一行备注解释了原因:


The Monitor class does not maintain state indicating that the Pulse method has been called. Thus, if you call Pulse when no threads are waiting, the next thread that calls Wait blocks as if Pulse had never been called.


也就是说,调用Pulse方法时,如果ready队列中没有处于WaitSleepJoin状态的线程,则即使以后有这样的线程出现了,即使之前Pulse过n次,也是没有作用滴

由于将t2线程先执行,所以在t1线程中Work方法执行时,由于Pulse状态的不维护,导致Wait的一直等待,也就是“死锁”,更加容易证明这个理论的做法是,单独只启动t1线程,在调用Monitor.Wait()之前调用Monitor.Pulse()方法,同样会出现死锁的情况,即:

static void Work()
{
lock (_lock)
{
Log();
Monitor.Pulse(_lock);
Monitor.Wait(_lock);
Log();
}
}

从而可以证明Pulse状态是无法维护的

ps.这几天看SharedCache代码,作者在SharedCacheThreadPool这个类中,也是利用Monitor的Wait和Pulse方法来实现“线程池”,个人觉得逻辑有些难以维护,尤其是做单元测试有些棘手

再ps,SharedCache在我看到的当前版本(应该是已公布的版本中最新的)中,有些代码是很值得重构的,例如说SharedCacheThreadPool这个类的组织,我将它包含的几个小类: WorkRequest, ThreadInfo单独提取出来并做了简单的重构之后才显得结构上比较清晰,或许是我个人能力问题吧,maybe...

再再ps,我个人总觉得SharedCache用Monitor的Wait和Pulse方法来实现线程池有点效率问题,不过我不知道m$的线程池内部是如何设计的,只是一句牢骚
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: