成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

讓我來告訴你,C# 線程本地存儲(chǔ)為什么線程間值不一樣

開發(fā) 前端
,為什么用 ThreadStatic 標(biāo)記的字段,只有第一個(gè)線程拿到了初始值,其他線程都是默認(rèn)值,讓我能不能幫他解答一下,尼瑪,我也不是神仙什么都懂,既然問了,那我試著幫他解答一下,也給后面類似疑問的朋友解個(gè)惑吧。

一:背景

1. 講故事

有朋友在微信里面問我,為什么用 ThreadStatic 標(biāo)記的字段,只有第一個(gè)線程拿到了初始值,其他線程都是默認(rèn)值,讓我能不能幫他解答一下,尼瑪,我也不是神仙什么都懂,既然問了,那我試著幫他解答一下,也給后面類似疑問的朋友解個(gè)惑吧。

二:為什么值不一樣

1. 問題復(fù)現(xiàn)

為了方便講述,定義一個(gè) ThreadStatic 的變量,然后用多個(gè)線程去訪問,參考代碼如下:

internal class Program
{
    [ThreadStatic]
    public static int num = 10;

    static void Main(string[] args)
    {
        Test();

        Console.ReadLine();
    }

    /// <summary>
    /// 1. 特性方式
    /// </summary>
    static void Test()
    {
        var t1 = new Thread(() =>
        {
            Debugger.Break();
            var j = num;
            Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId}, num={j}");

        });
        t1.Start();
        t1.Join();

        var t2 = new Thread(() =>
        {
            Debugger.Break();
            var j = num;
            Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId}, num={j}");
        });

        t2.Start();
    }
}

圖片圖片

從代碼中可以看到,確實(shí)如朋友所說,一個(gè)是num=10,一個(gè)是num=0 ,那為什么會(huì)出現(xiàn)這樣的情況呢?

2. 從匯編上尋找答案

作為C#程序員,真的需要掌握一點(diǎn)匯編,往往就能找到問題的突破口,先看一下thread1 中的 var j = num;所對(duì)應(yīng)的匯編代碼,參考如下:

D:\code\MyApplication\ConsoleApp7\Program.cs @ 27:
08893737 b9a0dd6808      mov     ecx,868DDA0h
0889373c ba04000000      mov     edx,4
08893741 e84a234e71      call    coreclr!JIT_GetSharedNonGCThreadStaticBase (79d75a90)
08893746 8b4814          mov     ecx,dword ptr [eax+14h]
08893749 894df8          mov     dword ptr [ebp-8],ecx

從匯編上可以看到,這個(gè) num=10 是來自于 eax+14h 的地址上,而 eax 是 JIT_GetSharedNonGCThreadStaticBase 函數(shù)的返回值,言外之意核心邏輯是在此方法里,可以到 coreclr 中找一下這段代碼,簡(jiǎn)化后如下:

HCIMPL2(void*, JIT_GetSharedNonGCThreadStaticBase, DomainLocalModule *pDomainLocalModule, DWORD dwClassDomainID)
{
    FCALL_CONTRACT;

    // Get the ModuleIndex
    ModuleIndex index = pDomainLocalModule->GetModuleIndex();

    // Get the relevant ThreadLocalModule
    ThreadLocalModule * pThreadLocalModule = ThreadStatics::GetTLMIfExists(index);

    // If the TLM has been allocated and the class has been marked as initialized,
    // get the pointer to the non-GC statics base and return
    if (pThreadLocalModule != NULL && pThreadLocalModule->IsPrecomputedClassInitialized(dwClassDomainID))
        return (void*)pThreadLocalModule->GetPrecomputedNonGCStaticsBasePointer();

    // If the TLM was not allocated or if the class was not marked as initialized
    // then we have to go through the slow path

    // Obtain the MethodTable
    MethodTable * pMT = pDomainLocalModule->GetMethodTableFromClassDomainID(dwClassDomainID);

    return HCCALL1(JIT_GetNonGCThreadStaticBase_Helper, pMT);
}

這段代碼非常有意思,已經(jīng)把 ThreadStatic 玩法的骨架圖給繪制出來了,大概意思是每個(gè)線程都有一個(gè) ThreadLocalBlock 結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體下有一個(gè) ThreadLocalModule 的字典,key 為 ModuleIndex, value 為 ThreadLocalModule,畫個(gè)簡(jiǎn)圖如下:

圖片圖片

從圖中可以看到 num 是放在 ThreadLocalModule 中的,具體的說就是此結(jié)構(gòu)的 m_pDataBlob 數(shù)組中,可以用 windbg 驗(yàn)證下。

0:008> r
eax=03077810 ebx=08baf978 ecx=79d75c10 edx=03110568 esi=053faa18 edi=053fa9b8
eip=08893746 esp=08baf8d8 ebp=08baf908 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ConsoleApp7!ConsoleApp7.Program.<>c.<Test>b__2_0+0x46:
08893746 8b4814          mov     ecx,dword ptr [eax+14h] ds:002b:03077824=0000000a

0:008> dt coreclr!ThreadLocalModule 03077810
   +0x000 m_pDynamicClassTable : (null) 
   +0x004 m_aDynamicEntries : 0
   +0x008 m_pGCStatics     : (null) 
   +0x00c m_pDataBlob      : [0]  ""

0:008> dp 03077810+0x14 L1
03077824  0000000a

有了這些前置知識(shí)后,接下來就簡(jiǎn)單了,如果當(dāng)前的 ThreadLocalModule 不存在就會(huì)調(diào)用 JIT_GetNonGCThreadStaticBase_Helper 函數(shù)在 m_pTLMTable 字段中添加一項(xiàng),接下來觀察下這個(gè)函數(shù)代碼,簡(jiǎn)化如下:

HCIMPL1(void*, JIT_GetNonGCThreadStaticBase_Helper, MethodTable * pMT)
{
    // Get the TLM
    ThreadLocalModule * pThreadLocalModule = ThreadStatics::GetTLM(pMT);

    // Check if the class constructor needs to be run
    pThreadLocalModule->CheckRunClassInitThrowing(pMT);

    // Lookup the non-GC statics base pointer
    base = (void*) pMT->GetNonGCThreadStaticsBasePointer();

    return base;
}

PTR_ThreadLocalModule ThreadStatics::GetTLM(ModuleIndex index, Module * pModule) //static
{
    // Get the TLM if it already exists
    PTR_ThreadLocalModule pThreadLocalModule = ThreadStatics::GetTLMIfExists(index);

    // If the TLM does not exist, create it now
    if (pThreadLocalModule == NULL)
    {
        // Allocate and initialize the TLM, and add it to the TLB's table
        pThreadLocalModule = AllocateAndInitTLM(index, pThreadLocalBlock, pModule);
    }

    return pThreadLocalModule;
}

上面這段代碼的步驟很清楚。

  • 創(chuàng)建 ThreadLocalModule
  • 初始化 MethodTable 類型的字段 pMT

這個(gè) pMT 非常重要,訓(xùn)練營(yíng)里的朋友都知道 MethodTable 是 C# 的 class 承載,言外之意就是判斷下這個(gè) class 有沒有被初始化,如果沒有初始化那就調(diào) 靜態(tài)構(gòu)造函數(shù),接下來的問題是 class 到底是哪一個(gè)類呢?

結(jié)合剛才匯編中的 mov edx,4 以及源碼發(fā)現(xiàn)是取 IL 元數(shù)據(jù)中的 Program,參考代碼及截圖如下:

FORCEINLINE MethodTable * GetMethodTableFromClassDomainID(DWORD dwClassDomainID)
    {
        DWORD rid = (DWORD)(dwClassDomainID) + 1;
        TypeHandle th = GetDomainFile()->GetModule()->LookupTypeDef(TokenFromRid(rid, mdtTypeDef));
        MethodTable * pMT = th.AsMethodTable();
        return pMT;
    }

圖片圖片

也可以用 windbg 在 JIT_GetNonGCThreadStaticBase_Helper 方法的 return 處下一個(gè)斷點(diǎn),參考如下:

0:008> r ecx
ecx=0564ef28
0:008> !dumpmt 0564ef28
EEClass:             056d14d0
Module:              0564db08
Name:                ConsoleApp7.Program
mdToken:             02000005
File:                D:\code\MyApplication\ConsoleApp7\bin\x86\Debug\net6.0\ConsoleApp7.dll
AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
BaseSize:            0xc
ComponentSize:       0x0
DynamicStatics:      false
ContainsPointers:    false
Slots in VTable:     8
Number of IFaces in IFaceMap: 0

到這里就真相大白了,thread1 在執(zhí)行時(shí),用 CheckRunClassInitThrowing 方法發(fā)現(xiàn) Program 沒有被靜態(tài)構(gòu)造過,所以就執(zhí)行了,即 num=10 ,當(dāng) thread2 執(zhí)行時(shí),發(fā)現(xiàn)已經(jīng)被構(gòu)造過了,所以就不再執(zhí)行靜態(tài)構(gòu)造函數(shù),所以就成了默認(rèn)值 num=0。

3. 如何復(fù)驗(yàn)?zāi)愕慕Y(jié)論

剛才我說 thread1 做了一個(gè)是否執(zhí)行靜態(tài)構(gòu)造的判斷,其實(shí)這里我可以做個(gè)手腳,在 Main 之前先把 Program 靜態(tài)函數(shù)給執(zhí)行掉,按理說 thread1 和 thread2 此時(shí)都會(huì)是默認(rèn)值 num=0,對(duì)不對(duì),哈哈,試一試唄,簡(jiǎn)化代碼如下:

internal class Program
    {
        [ThreadStatic]
        public static int num = 10;

        /// <summary>
        /// 先于 main 執(zhí)行
        /// </summary>
        static Program()
        {
        }

        static void Main(string[] args)
        {
            Test();

            Console.ReadLine();
        }
    }

圖片圖片

哈哈,此時(shí)都是 0 了,也就再次驗(yàn)證了我的結(jié)論。

三:總結(jié)

在 C# 開發(fā)中經(jīng)常會(huì)有一些疑惑,如果不了解匯編,C++ ,相信你會(huì)陷入到很多的魔法使用中而苦于不能獨(dú)自解惑的遺憾。

責(zé)任編輯:武曉燕 來源: 一線碼農(nóng)聊技術(shù)
相關(guān)推薦

2012-12-21 10:42:49

數(shù)據(jù)分析中土世界數(shù)據(jù)可視化項(xiàng)目

2020-02-14 14:36:23

DevOps落地認(rèn)知

2012-03-07 17:24:10

戴爾咨詢

2012-12-20 10:17:32

IT運(yùn)維

2021-07-12 23:53:22

Python交換變量

2017-11-03 07:57:19

2023-03-20 08:19:23

GPT-4OpenAI

2021-05-14 10:28:54

線程協(xié)程數(shù)量

2018-07-10 11:05:55

Emoji蘋果Google

2017-05-25 15:02:46

聯(lián)宇益通SD-WAN

2016-05-09 18:40:26

VIP客戶緝拿

2015-10-19 12:33:01

華三/新IT

2018-02-28 10:16:51

程序員技能 PHP

2022-04-29 08:00:36

web3區(qū)塊鏈比特幣

2010-11-24 20:18:48

Microsoft L統(tǒng)一溝通

2012-07-18 02:05:02

函數(shù)語言編程語言

2018-02-27 12:01:12

程序員PHP編程語言

2012-04-27 09:19:08

2018-05-09 15:42:24

新零售

2009-08-20 16:52:41

C# Hello Wo
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 欧美午夜激情在线 | 激情亚洲 | 亚洲高清在线免费观看 | 亚洲综合婷婷 | 逼逼网 | 免费观看一级特黄欧美大片 | 亚洲精品视频久久 | 欧美一区二区在线 | 精品不卡 | 国产视频一区二区 | 国产伦精品一区二区三区高清 | 久久久久99 | 福利影院在线看 | 亚洲性免费 | 亚洲va欧美va天堂v国产综合 | 国产大学生情侣呻吟视频 | 欧美成年人视频在线观看 | 中文字幕乱码视频32 | 自拍偷拍3p | 国产日韩欧美在线 | 成人精品国产一区二区4080 | 亚洲欧美日韩中文在线 | 亚洲综合成人网 | 欧美一区二区在线播放 | 国产一区二区三区视频 | 久久久综合精品 | 国产精品欧美一区二区 | 国产高潮好爽受不了了夜夜做 | 欧美精品 在线观看 | 久久精品欧美一区二区三区麻豆 | 欧美性猛交一区二区三区精品 | 日韩av资源站 | 欧美高清视频一区 | 国产午夜亚洲精品不卡 | 久久精品色欧美aⅴ一区二区 | 国产精品视频一区二区三 | 久久69精品久久久久久久电影好 | 久久极品| 欧美日韩一区二区视频在线观看 | 天天插天天搞 | 久久欧美高清二区三区 |