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);
}
为了真正了解是否有更简单的方法来实现
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 级别以下的调查工作都是他的,我只是在这里将其编译为答案。从最近回答)一个类似的问题开始,我可以指出 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()
}