最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺析C#線程本地存儲中為什么線程間值不一樣

 更新時間:2024年01月24日 09:51:08   作者:一線碼農  
這篇文章主要想來和大家一起討論一下C#線程本地存儲中為什么線程間值不一樣,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下

一:背景

講故事

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

二:為什么值不一樣

1. 問題復現(xiàn)

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

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();
    }
}

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

2. 從匯編上尋找答案

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

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

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

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 玩法的骨架圖給繪制出來了,大概意思是每個線程都有一個 ThreadLocalBlock 結構體,這個結構體下有一個 ThreadLocalModule 的字典,key 為 ModuleIndex, value 為 ThreadLocalModule,畫個簡圖如下:

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

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

有了這些前置知識后,接下來就簡單了,如果當前的 ThreadLocalModule 不存在就會調用 JIT_GetNonGCThreadStaticBase_Helper 函數(shù)在 m_pTLMTable 字段中添加一項,接下來觀察下這個函數(shù)代碼,簡化如下:

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

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

結合剛才匯編中的 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 處下一個斷點,參考如下:

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í)行時,用 CheckRunClassInitThrowing 方法發(fā)現(xiàn) Program 沒有被靜態(tài)構造過,所以就執(zhí)行了,即 num=10 ,當 thread2 執(zhí)行時,發(fā)現(xiàn)已經(jīng)被構造過了,所以就不再執(zhí)行靜態(tài)構造函數(shù),所以就成了默認值 num=0

3. 如何復驗你的結論

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

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

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

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

            Console.ReadLine();
        }
    }

哈哈,此時都是 0 了,也就再次驗證了我的結論。

以上就是淺析C#線程本地存儲中為什么線程間值不一樣的詳細內容,更多關于C#線程本地存儲的資料請關注腳本之家其它相關文章!

相關文章

  • C#實現(xiàn)Ping的方法小結

    C#實現(xiàn)Ping的方法小結

    這篇文章主要介紹了C#實現(xiàn)Ping的方法,以兩個實例形式形式較為詳細的分析了C#實現(xiàn)ping功能的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-08-08
  • C# 格式化字符串的實現(xiàn)代碼

    C# 格式化字符串的實現(xiàn)代碼

    這篇文章主要介紹了C# 格式化字符串的實現(xiàn)代碼,需要的朋友可以參考下
    2016-11-11
  • c#類的使用示例

    c#類的使用示例

    這篇文章主要介紹了c#類的使用示例,還有我學習時的筆記,需要的朋友可以參考下
    2014-04-04
  • C#動態(tài)生成PictureBox并指定圖片的方法

    C#動態(tài)生成PictureBox并指定圖片的方法

    這篇文章主要介紹了C#動態(tài)生成PictureBox并指定圖片的方法,實例分析了C#圖形控件的動態(tài)生成及使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • C#實現(xiàn)運行狀態(tài)堆疊柱狀圖

    C#實現(xiàn)運行狀態(tài)堆疊柱狀圖

    這篇文章主要為大家詳細介紹了C#實現(xiàn)運行狀態(tài)堆疊柱狀圖,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • 輕松學習C#的正則表達式

    輕松學習C#的正則表達式

    輕松學習C#的正則表達式,對C#的正則表達式感興趣的朋友可以參考本篇文章,幫助大家更靈活的運用C#的正則表達式
    2015-11-11
  • C#實現(xiàn)銀行家算法

    C#實現(xiàn)銀行家算法

    這篇文章主要為大家詳細介紹了C#實現(xiàn)銀行家算法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • 使用C#代碼統(tǒng)計Word文檔的單詞、字符、段落、行數(shù)和頁數(shù)

    使用C#代碼統(tǒng)計Word文檔的單詞、字符、段落、行數(shù)和頁數(shù)

    對單詞、字符、段落、行數(shù)和頁數(shù)進行準確統(tǒng)計,對于實現(xiàn)精確的文檔分析至關重要,通過細致地追蹤這些指標,作者可以更深入地了解文檔的長度、結構和整體構成,在本文中,我們將介紹如何使用 C# 代碼統(tǒng)計 Word 文檔中的單詞、字符、段落、行數(shù)和頁數(shù),需要的朋友可以參考下
    2025-12-12
  • C#連接Oracle數(shù)據(jù)庫字符串(引入DLL)的方式

    C#連接Oracle數(shù)據(jù)庫字符串(引入DLL)的方式

    這篇文章主要給大家介紹了關于C#連接Oracle數(shù)據(jù)庫字符串(引入DLL)的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-08-08
  • C#實現(xiàn)文件上傳和接收功能

    C#實現(xiàn)文件上傳和接收功能

    Web應用開發(fā)中,文件上傳是一個常見且重要的功能,它不僅涉及到前端用戶界面的設計,還涉及到后端服務器如何高效、安全地處理上傳的數(shù)據(jù),本文將詳細介紹如何基于C#實現(xiàn)文件上傳和接收功能,需要的朋友可以參考下
    2025-08-08

最新評論

庆城县| 贵港市| 长乐市| 东乌| 宜州市| 天全县| 马公市| 菏泽市| 望都县| 赤峰市| 宁津县| 顺义区| 东港市| 东乌珠穆沁旗| 怀远县| 柞水县| 荔波县| 乌审旗| 宁陵县| 西林县| 肇东市| 油尖旺区| 垫江县| 巴林右旗| 克山县| 新绛县| 开原市| 精河县| 黑山县| 浦县| 连江县| 博野县| 比如县| 荣成市| 闵行区| 土默特右旗| 紫金县| 惠州市| 喀喇| 丁青县| 东光县|