国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

C# 線程本地存儲(chǔ) 為什么線程間值不一樣

這篇具有很好參考價(jià)值的文章主要介紹了C# 線程本地存儲(chǔ) 為什么線程間值不一樣。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

一:背景

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

C# 線程本地存儲(chǔ) 為什么線程間值不一樣

從代碼中可以看到,確實(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)圖如下:

C# 線程本地存儲(chǔ) 為什么線程間值不一樣

從圖中可以看到 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)練營里的朋友都知道 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;
    }

C# 線程本地存儲(chǔ) 為什么線程間值不一樣

也可以用 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();
        }
    }

C# 線程本地存儲(chǔ) 為什么線程間值不一樣

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

三:總結(jié)

在 C# 開發(fā)中經(jīng)常會(huì)有一些疑惑,如果不了解匯編,C++ ,相信你會(huì)陷入到很多的魔法使用中而苦于不能獨(dú)自解惑的遺憾。文章來源地址http://www.zghlxwxcb.cn/news/detail-818773.html

C# 線程本地存儲(chǔ) 為什么線程間值不一樣

到了這里,關(guān)于C# 線程本地存儲(chǔ) 為什么線程間值不一樣的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 為什么使用線程池?解釋下線程池參數(shù)?

    (1)降低資源消耗:提高線程利用率,降低創(chuàng)建和銷毀線程的消耗。 (2)提高響應(yīng)速度:任務(wù)來了,直接有線程可用可執(zhí)行,而不是線創(chuàng)建線程再執(zhí)行。 (3)提高線程的可管理性;線程是稀缺資源,使用線程池可以統(tǒng)一分配調(diào)優(yōu)監(jiān)控。 (1)corePoolSize:代表核心線程數(shù),也

    2024年02月16日
    瀏覽(31)
  • 為什么要用線程池?

    線程池是一種管理和復(fù)用線程資源的機(jī)制,它由一個(gè)線程池管理器和一組工作線程組成。線程池管理器負(fù)責(zé)創(chuàng)建和銷毀線程池,以及管理線程池中的工作線程。工作線程則負(fù)責(zé)執(zhí)行具體的任務(wù)。 線程池的主要作用是管理和復(fù)用線程資源,避免了線程的頻繁創(chuàng)建和銷毀所帶來的

    2024年02月06日
    瀏覽(22)
  • 為什么arrayList線程不安全?

    ????????ArrayList是Java中的一種動(dòng)態(tài)數(shù)組,它在內(nèi)部使用數(shù)組來存儲(chǔ)元素。ArrayList的線程不安全性主要體現(xiàn)在多線程并發(fā)訪問和修改同一個(gè)ArrayList實(shí)例時(shí)可能出現(xiàn)的問題。 ????????當(dāng)多個(gè)線程同時(shí)對(duì)ArrayList進(jìn)行修改操作時(shí),可能會(huì)導(dǎo)致數(shù)據(jù)不一致或者出現(xiàn)異常。這是因?yàn)?/p>

    2024年02月12日
    瀏覽(22)
  • ArrayList為什么不是線程安全的,如何保證線程安全?

    ArrayList為什么不是線程安全的,如何保證線程安全?

    官方曰, 線程安全就是多線程訪問時(shí),采?了加鎖機(jī)制,當(dāng)?個(gè)線程訪問該類的某個(gè)數(shù)據(jù)時(shí),進(jìn)?保護(hù),其他線程不能進(jìn)?訪問直到該線程讀取完,其他線程才可使?。不會(huì)出現(xiàn)數(shù)據(jù)不?致或者數(shù)據(jù)污染。線程不安全就是不提供數(shù)據(jù)訪問保護(hù),有可能出現(xiàn)多個(gè)線程先后更改數(shù)

    2024年02月07日
    瀏覽(45)
  • ConcurrentHashMap為什么是線程安全的?

    ConcurrentHashMap為什么是線程安全的?

    1、ConcurrentHashMap的原理和結(jié)構(gòu) 我們都知道Hash表的結(jié)構(gòu)是數(shù)組加鏈表,就是一個(gè)數(shù)組中,每一個(gè)元素都是一個(gè)鏈表,有時(shí)候也把會(huì)形象的把數(shù)組中的每個(gè)元素稱為一個(gè)“桶”。在插入元素的時(shí)候,首先通過對(duì)傳入的鍵(key),進(jìn)行一個(gè)哈希函數(shù)的處理,來確定元素應(yīng)該存放于

    2024年02月07日
    瀏覽(24)
  • js為什么是單線程?

    類比操作系統(tǒng),多線程問題有: 單一資源多線程搶占,引起死鎖問題; 線程間同步數(shù)據(jù)問題; 為了簡(jiǎn)單: 更簡(jiǎn)單的dom渲染。js可以操控dom,而一般來說一個(gè)網(wǎng)頁一份dom文件,多線程操作dom如果多線程修改dom便容易出現(xiàn)各種問題(例如A線程刪除一個(gè)dom,而B線程在修改此dom容

    2024年02月07日
    瀏覽(27)
  • 面試題:HashMap線程不安全 ConcurrentHashMap為什么線程安全

    面試的時(shí)候先會(huì)喊你說說集合,那些集合線程不安全?當(dāng)你說了 HashMap 線程不安全,面試官可能會(huì)進(jìn)一步詢問你是否了解 ConcurrentHashMap ,以及它是如何實(shí)現(xiàn)線程安全的。 ArrayList、LinkedList、TreeSet、HashSet、 HashMap 、TreeMap等都是線程不安全的。 HashTable 是線程安全的。 來看個(gè)例

    2024年04月23日
    瀏覽(20)
  • SimpleDateFormat為什么是線程不安全的?

    SimpleDateFormat為什么是線程不安全的?

    大家好,我是哪吒。 在日常開發(fā)中,Date工具類使用頻率相對(duì)較高,大家通常都會(huì)這樣寫: 這很簡(jiǎn)單啊,有什么爭(zhēng)議嗎? 你應(yīng)該聽過“時(shí)區(qū)”這個(gè)名詞,大家也都知道,相同時(shí)刻不同時(shí)區(qū)的時(shí)間是不一樣的。 因此在使用時(shí)間時(shí),一定要給出時(shí)區(qū)信息。 對(duì)于當(dāng)前的上海時(shí)區(qū)和

    2024年02月20日
    瀏覽(23)
  • Redis為什么是單線程的

    首先,現(xiàn)在的CPU一般都是由多個(gè)核心組成,每個(gè)核心可以認(rèn)為是一個(gè)獨(dú)立的處理器,它們能夠并行地處理任務(wù)。所以,如果我們的CPU是多核的,但是程序是單線程的,那么執(zhí)行程序時(shí),這個(gè)線程在某一個(gè)時(shí)刻只能在一個(gè)核心上運(yùn)行,而其它的核心卻是空閑的(如果沒有其他程

    2024年02月11日
    瀏覽(23)
  • 存儲(chǔ)過程為什么使用DELIMITER $$,存儲(chǔ)過程的詳細(xì)運(yùn)用解釋

    這是正確的存儲(chǔ)過程寫法,可以成功執(zhí)行,相比較上圖的報(bào)錯(cuò),增加了DELIMITER,簡(jiǎn)單解釋下這個(gè)命令的用途,在MySQL中每行命令都是用“;”結(jié)尾,回車后自動(dòng)執(zhí)行,在存儲(chǔ)過程中“;”往往不代表指令結(jié)束,馬上運(yùn)行,而DELIMITER原本就是“;”的意思,因此用這個(gè)命令轉(zhuǎn)換一

    2024年01月25日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包