C# Monitor.Enter() 源码追踪

Stella981
• 阅读 629

start

[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical, __DynamicallyInvokable]
public static extern void Enter(object obj);

src/vm/ecall.cpp

FCFuncStart(gMonitorFuncs)
    FCFuncElement("Enter", JIT_MonEnter)
    FCFuncElement("Exit", JIT_MonExit)
    FCFuncElement("TryEnterTimeout", JIT_MonTryEnter)
    FCFuncElement("ObjWait", ObjectNative::WaitTimeout)
    FCFuncElement("ObjPulse", ObjectNative::Pulse)
    FCFuncElement("ObjPulseAll", ObjectNative::PulseAll)
    FCFuncElement("ReliableEnter", JIT_MonReliableEnter)
FCFuncEnd()

next -> JIT_MonEnter

clr/src/vm/jithelpers.cpp

HCIMPL_MONHELPER (JIT_MonEnterWorker_Portable, Object* obj)
{
    .......
}

相关参数 :

SyncBlock* syncBlock = NULL;//😢同步索引块
ObjHeader* objHeader = NULL;//对象头
int spincount = 50;//spin - 轮询 😢采用了轮询机制
const int MaxSpinCount = 20000 * g_SystemInfo.dwNumberOfProcessors;
LONG oldvalue, state;
DWORD tid;

objHeader = obj->GetHeader();//获取请求头

OBJECTREF objRef = ObjectToOBJECTREF(obj);//获取请求头的引用

objRef->EnterObjMonitor();//调用EnterObjMonitor

源码追踪

1.GetHeader()方法获取对象头ObjHeader,在ObjHeader里有对EnterObjMonitor()方法的定义:

clr/src/vm/object.cpp

// 获取请求头
// 访问对象上负偏移量的ObjHeader(因为高速缓存线路)
// Access the ObjHeader which is at a negative offset on the object (because of
// cache lines)
ObjHeader   *GetHeader()
{
    LEAF_CONTRACT;
    return PTR_ObjHeader(PTR_HOST_TO_TADDR(this) - sizeof(ObjHeader));
}

next->查看ObjHeader的EnterObjMonitor的定义

clr/src/vm/syncblk.cpp

void ObjHeader::EnterObjMonitor()
{
    WRAPPER_CONTRACT;
    GetSyncBlock()->EnterMonitor();
}

又调用了GetSyncBlock的EnterMonitor 追下去。

clr/src/vm/syncblk.h

void EnterMonitor()
{
    WRAPPER_CONTRACT;
    m_Monitor.Enter();
}

调用了m_Monitor的Enter

查看m_Monitor的定义:

protected:
AwareLock  m_Monitor;                    // the actual monitor

继续 查看AwareLock的Enter方法定义,感觉已经越来越近了😢

void AwareLock::Enter()
{
    CONTRACTL
    {
        INSTANCE_CHECK;
        THROWS;
        GC_TRIGGERS;
        MODE_ANY;
        INJECT_FAULT(COMPlusThrowOM(););
    }
    CONTRACTL_END;

    Thread  *pCurThread = GetThread();

    for (;;) 
    {
        // Read existing lock state.
        volatile LONG state = m_MonitorHeld;

        if (state == 0) 
        {
            // Common case: lock not held, no waiters. Attempt to acquire lock by
            //常见情况:锁没锁,没有服务员。试图获得锁定
            // switching lock bit.
            //开关锁。
            if (FastInterlockCompareExchange((LONG*)&m_MonitorHeld, 1, 0) == 0)//cas 修改值。
            {
                break;
            }
        } 
        else 
        {
            // It's possible to get here with waiters but no lock held, but in this
            // case a signal is about to be fired which will wake up a waiter. So
            // for fairness sake we should wait too.
            // Check first for recursive lock attempts on the same thread.
            if (m_HoldingThread == pCurThread)//如果为当前线程
            {    
                goto Recursion;
            }

            // Attempt to increment this count of waiters then goto contention
            // handling code.
            if (FastInterlockCompareExchange((LONG*)&m_MonitorHeld, (state + 2), state) == state)
            {
                goto MustWait;
            }
        }

    }

    // We get here if we successfully acquired the mutex.
    m_HoldingThread = pCurThread;
    m_Recursion = 1;
    pCurThread->IncLockCount();

#if defined(_DEBUG) && defined(TRACK_SYNC)
    {
        // The best place to grab this is from the ECall frame
        Frame   *pFrame = pCurThread->GetFrame();
        int      caller = (pFrame && pFrame != FRAME_TOP
                            ? (int) pFrame->GetReturnAddress()
                            : -1);
        pCurThread->m_pTrackSync->EnterSync(caller, this);
    }
#endif

    return;

MustWait:
    // Didn't manage to get the mutex, must wait. //继续等待
    EnterEpilog(pCurThread);
    return;

Recursion:
    // Got the mutex via recursive locking on the same thread.
    _ASSERTE(m_Recursion >= 1);
    m_Recursion++;//递归次数加1
#if defined(_DEBUG) && defined(TRACK_SYNC)
    // The best place to grab this is from the ECall frame
    Frame   *pFrame = pCurThread->GetFrame();
    int      caller = (pFrame && pFrame != FRAME_TOP ? (int) pFrame->GetReturnAddress() : -1);
    pCurThread->m_pTrackSync->EnterSync(caller, this);
#endif
}

从上面的代码我们可以看到,先使用GetThread()获取当前的线程,然后取出m_MonitorHeld字段,如果现在没有线程进入临界区,则设置该字段的状态,然后将m_HoldingThread设置为当前线程,从这一点上来这与Win32的过程应该是一样的。

如果从m_MonitorHeld字段看,有线程已经进入临界区则分两种情况:第一,是否已进入的线程如当前线程是同一个线程,如果是,则把m_Recursion递加,如果不是,则通过EnterEpilog( pCurThread)方法,当前线程进入线程等待队列。

通过上面的文字描述和代码的跟踪,在我们的大脑中应该有这样一张图了:

C# Monitor.Enter() 源码追踪


confirm

clr/src/vm/syncblk.h

ObjHeader has an index to a SyncBlock. This index is 0 for the bulk of all

ObjHeader有一个指向同步块的索引。大多数情况下,这个指数是0


相关链接

https://github.com/SSCLI/sscli20_20060311

http://www.aspphp.online/bianchen/dnet/gydnet/201701/14624.html


此次只是简单的追踪了一番,部分内容可能不详细。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这