c#:Monitor.Wait是如何实现的?

问题描述 投票:0回答:2

C#的system.threading.monitor类内部的Monitor.Wait()方法是如何实现的?

https://www.codeproject.com/Articles/28785/Thread-synchronization-Wait-and-Pulse-demystified

从概念上讲,我设想的是这样的:

   class Monitor {
       public static Wait(object o) 
       {
           // Release Lock
           Monitor.Exit(o);

           // Spinlock until another Thread acquires Lock
           while(!Monitor.isEnter(o));

           // Wait to re-acquire lock
           Monitor.Enter(o);
       }
   }

这么准确吗?或者我有什么遗漏的吗?

这是典型的监视器类示例,我正在考虑将其作为前面代码的基础。

using System.Threading;

readonly object o = new object();

// In Thread #1: (Where appropriate) 
lock(o) {
    Monitor.Wait(o);
}

//In Thread #2:  (Where appropriate) 
lock(o) {
    Monitor.Pulse(o);  
}

Lock(o) 当然是一个内置的 C# 快捷方式:

try {
    Monitor.Enter(o);
    {
        //Lock Block Here
    }
}
finally {
    Monitor.Exit(o);
}
c# multithreading condition-variable
2个回答
5
投票

为了真正了解是否有更简单的方法来实现

Monitor.Wait
,我们必须研究它在低级别上的运作方式。实际的实现最终是用 C 编写的,对我们来说是隐藏的,但特别是对于
Monitor.Wait(object)
,我们可以通过以下方式跟踪调用链;

Monitor.Wait(o)
-- return Monitor.Wait(o, -1, false)

Monitor.Wait(o, -1, false)
-- Monitor.ObjWait(false [exitContext], -1 [millisecondsTimeout], o)

从这里甚至在 ILSpy 中也很难看到发生了什么。根据 Tigran 到

Monitor
对象源的链接,我们在源中留下以下内容;

    /*========================================================================
** Waits for notification from the object (via a Pulse/PulseAll). 
** timeout indicates how long to wait before the method returns.
** This method acquires the monitor waithandle for the object 
** If this thread holds the monitor lock for the object, it releases it. 
** On exit from the method, it obtains the monitor lock back. 
** If exitContext is true then the synchronization domain for the context 
** (if in a synchronized context) is exited before the wait and reacquired 
**
    ** Exceptions: ArgumentNullException if object is null.
========================================================================*/
    [System.Security.SecurityCritical]  // auto-generated
    [ResourceExposure(ResourceScope.None)]
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    private static extern bool ObjWait(bool exitContext, int millisecondsTimeout, Object obj)

对于它正在做什么以及按什么顺序进行的描述相当不言自明。然而,它的精确实现是由包含关键代码的各种

private static extern
方法包装的。

extern
指定实际实现位于另一个程序集中。当访问非托管代码时(不是这里的情况),它可以与
DllImport
一起使用,或者可以是 extern 别名。从这里开始,根据 SO post 询问在哪里可以找到 extern 方法的实现,您必须查看 C 代码本身,它可以在 Core CLR 中找到(信用 Scott Chamberlain)。

从这里我们将看到

ObjWait()
的 C 方法实现,它将 (第 1027 行)映射到 CLR 中的 ObjectNative::WaitTimeout

FCIMPL3(FC_BOOL_RET, ObjectNative::WaitTimeout, CLR_BOOL exitContext, INT32 Timeout, Object* pThisUNSAFE) { FCALL_CONTRACT; BOOL retVal = FALSE; OBJECTREF pThis = (OBJECTREF) pThisUNSAFE; HELPER_METHOD_FRAME_BEGIN_RET_1(pThis); if (pThis == NULL) COMPlusThrow(kNullReferenceException, W("NullReference_This")); if ((Timeout < 0) && (Timeout != INFINITE_TIMEOUT)) COMPlusThrowArgumentOutOfRange(W("millisecondsTimeout"), W("ArgumentOutOfRange_NeedNonNegNum")); retVal = pThis->Wait(Timeout, exitContext); HELPER_METHOD_FRAME_END(); FC_RETURN_BOOL(retVal); } FCIMPLEND

在讨论这个问题之前,值得看一下

this(也归功于 Scott Chamberlain),其中指出;

FCall 在托管代码中被标识为设置了 MethodImplOptions.InternalCall 位的外部方法。

这解释了我们与

ObjWait()

ObjectNative::WaitTimeout
 的链接。因此,进一步分解,我们可以看到基本的 
null
 和参数检查,如果是的话,会引发适当的异常。关键是对 
pThis->Wait()
 的调用...此时我还无法进一步追踪...。

从这里我们到达

Object::Wait

第531行),然后前往SyncBlock::Wait
第3442行)。至此,我们已经掌握了实现的大部分内容,而且还有很多内容。

考虑到上述所有内容并回到您所要求的更简单的实现,我会对简化持谨慎态度

Monitor.Wait()

。幕后发生了很多事情,很容易犯错误,并且在替代实现中存在潜在的错误。
编辑

严重向Scott Chamberlain

表示感谢,他完成了 ILSpy 级别以下的大部分调查并深入/调试了 C 代码堆栈。几乎所有 ILSpy 级别以下的调查工作都是他的,我只是在这里将其编译为答案。

从最近

0
投票
(和

回答)一个类似的问题开始,我可以指出 Joe Duffy 2007 年博客文章中关于如何实现条件变量(即Monitor.Wait/Monitor.Pulse

)的出色概述解释:
条件变量的实现略有不同。每个 

CLR 线程对象
有一个专用于它的

单个事件对象。首先 当线程在条件变量上调用 Wait 时,事件是惰性的 分配。然后线程简单地放置它自己的线程本地 事件进入

与监视器关联的事件列表
。注册 该事件还需要对同步块进行通货膨胀(如果还没有) 已经发生了,因为显然事件列表不能存储在 对象头。当 Pulse 发生时,脉冲线程只是 发出列表中第一个事件的信号...当
PulseAll
发生时,脉冲线程 遍历整个列表并对每个列表发出信号。
还有一个
最近的 github 讨论

,对
Monitor.Wait

的调用堆栈进行了更深入的研究(尤其是在这个评论中)

因此,它默认的工作方式是让 ManualResetEvent 与调用 

Monitor.Wait()

 的线程关联
从你的例子来看:
readonly object o = new object();

lock(o) {
    Monitor.Wait(o); // if no syncblock
    // 1. o.SyncBlock = new SyncBlock()
    // 2. o.SyncBlock.ListEvents = new List<ManualResetEvent>()
    // 3. Thread.CurrentThread.ManualResetEvent = new ManualResetEvent(false)
    // 4. o.SyncBlock.ListEvents.Add(Thread.CurrentThread.ManualResetEvent)
    // 5. Monitor.Exit(o)
    // 6. Thread.CurrentThread.ManualResetEvent.WaitOne();
    // 7. Monitor.Enter(o)
    
}

lock(o) {
    Monitor.Pulse(o);
    // 1. o.SyncBlock.List.First.Set()  
}

© www.soinside.com 2019 - 2024. All rights reserved.