博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# Monitor实现
阅读量:4606 次
发布时间:2019-06-09

本文共 5753 字,大约阅读时间需要 19 分钟。

Monitor的code如下,非常简单:

public static class Monitor {    public static extern void Enter(Object obj);    public static void Enter(Object obj, ref bool lockTaken)    {        if (lockTaken)            ThrowLockTakenException();        ReliableEnter(obj, ref lockTaken);        Contract.Assert(lockTaken);    }    private static extern void ReliableEnter(Object obj, ref bool lockTaken);    public static void TryEnter(Object obj, ref bool lockTaken)    {        if (lockTaken)            ThrowLockTakenException();        ReliableEnterTimeout(obj, 0, ref lockTaken);    }    private static extern void ReliableEnterTimeout(Object obj, int timeout, ref bool lockTaken);        public static extern void Exit(Object obj);            public static bool Wait(Object obj, int millisecondsTimeout, bool exitContext)    {        if (obj == null)            throw (new ArgumentNullException("obj"));        return ObjWait(exitContext, millisecondsTimeout, obj);    }    private static extern bool ObjWait(bool exitContext, int millisecondsTimeout, Object obj);    public static void Pulse(Object obj)    {        if (obj == null)        {            throw new ArgumentNullException("obj");        }        Contract.EndContractBlock();        ObjPulse(obj);    }    private static extern void ObjPulse(Object obj);            public static void PulseAll(Object obj)    {        if (obj == null)        {            throw new ArgumentNullException("obj");        }        Contract.EndContractBlock();        ObjPulseAll(obj);    }    private static extern void ObjPulseAll(Object obj);        public static bool IsEntered(object obj)    {        if (obj == null)            throw new ArgumentNullException("obj");        return IsEnteredNative(obj);    }   private static extern bool IsEnteredNative(Object obj);    }

核心方法就是Enter和Exit,其中lock关键字就是这2个方法的一个封装,剩下的Wait、Pulse和PulseAll也是很重要的方法,但是平时运用的比较少。所以这里重点说说Wait、Pulse和PulseAll方法。

线程优先顺序: 【等待队列】->【就绪队列】->【拥有锁线程】这个是重点,下文多次会提到,其中的微妙关系的核心也来源于这个执行顺序。

MSDN官方备注:同步的对象包含若干引用,其中包括对当前拥有锁的线程的引用、对就绪队列的引用和对等待队列的引用。我的提醒:竞争对象锁的线程都是处于就绪队列中

1.Monitor.Wait方法

当线程调用 Wait 时,它释放对象的锁并进入对象的等待队列,对象的就绪队列中的下一个线程(如果有)获取锁并拥有对对象的独占使用。Wait()就是交出锁的使用权,使线程处于阻塞状态,直到再次获得锁的使用权。
2.Monitor.Pulse方法
当前线程调用此方法以便向队列中的下一个线程发出锁的信号。接收到脉冲后,等待线程就被移动到就绪队列中。在调用 Pulse 的线程释放锁后,就绪队列中的下一个线程(不一定是接收到脉冲的线程)将获得该锁。pulse()并不会使当前线程释放锁。

当一个线程尝试着lock一个同步对象的时候,该线程就在就绪队列中排队。一旦没人拥有该同步对象,就绪队列中的线程就可以占有该同步对象。这也是我们平时最经常用的lock方法。为了其他的同步目的,占有同步对象的线程也可以暂时放弃同步对象,并把自己流放到等待队列中去,这就是Monitor.Wait;由于该线程放弃了同步对象,其他在就绪队列的排队者就可以进而拥有同步对象。比起就绪队列来说,在等待队列中排队的线程更像是二等公民:他们不能自动得到同步对象,甚至不能自动升舱到就绪队列而Monitor.Pulse的作用就是开一次门,使得一个正在等待队列中的线程升舱到就绪队列相应的Monitor.PulseAll则打开门放所有等待队列中的线程到就绪队列

class Program    {        static void Main(string[] args)        {            new Thread(A).Start();            new Thread(B).Start();            new Thread(C).Start();            Console.ReadLine();        }        static object lockObj = new object();        static void A()        {            lock (lockObj) //进入就绪队列             {                Thread.Sleep(1000);                Monitor.Pulse(lockObj);                Monitor.Wait(lockObj); //自我流放到等待队列            }            Console.WriteLine("A exit...");        }        static void B()        {            Thread.Sleep(500);            lock (lockObj) //进入就绪队列             {                Monitor.Pulse(lockObj);            }            Console.WriteLine("B exit...");        }        static void C()        {            Thread.Sleep(800);            lock (lockObj) //进入就绪队列             { }            Console.WriteLine("C exit...");        }    }

假设线程A先得到了同步对象,它就登记到同步对象lockObj的“拥有者引用”中。线程B和C要求拥有同步对象,他们将在“就绪队列”排队,|--(拥有锁的线程) A | |--(就绪队列) B,C | |--(等待队列)。

线程A用Pulse发出信号,允许第一个正在"等待队列"中的线程进入到”就绪队列“。但由于等待列是空的,什么事也没有发生。线程A用Wait放弃同步对象,并把自己放入"等待队列"。B,C已经在就绪队列中,因此其中的一个得以获得同步对象(假定是B)。B成了同步对象的拥有者。C现在还是候补委员,可以自动获得空缺。而A则被关在门外,不能自动获得空缺。 |--(拥有锁的线程) B ||--(就绪队列) C | |--(等待队列) A

线程B用Pulse发出信号开门,第一个被关在门外的A被允许放入到就绪队列,现在C和A都成了候补委员,一旦同步对象空闲,都有机会得它。 |--(拥有锁的线程) B | |--(就绪队列) C,A | |--(等待队列)

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 Program2    {        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命令组合,达到事件机的作用。

转载于:https://www.cnblogs.com/majiang/p/7891338.html

你可能感兴趣的文章
【前端开发】 5分钟创建 Mock Server
查看>>
java 从键盘录入的三种方法
查看>>
使用jQuery和YQL,以Ajax方式加载外部内容
查看>>
pyspider 示例
查看>>
JAVA 笔记(一)
查看>>
c# 范型Dictionary实用例子
查看>>
C#实现动态页面静态化
查看>>
可选参数、命名参数、.NET的特殊类型、特性
查看>>
利用CGLib实现动态代理实现Spring的AOP
查看>>
面试之SQL(1)--选出选课数量>=2的学号
查看>>
IIS处理并发请求时出现的问题
查看>>
优先队列小结
查看>>
线程安全与可重入函数之间的区别与联系
查看>>
{Nodejs} request URL 中文乱码
查看>>
异常及日志使用与项目打包
查看>>
努力,时间,坚持,自律
查看>>
Hadoop2.6.0 动态增加节点
查看>>
图论的一些概念、定理
查看>>
WebView用法
查看>>
Lecture 3: Planning by Dynamic Programming
查看>>