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

聊一聊 C# 的線程本地存儲TLS到底是什么

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

一:背景

1. 講故事

有朋友在后臺留言讓我說一下C#的 ThreadStatic 線程本地存儲是怎么玩的?這么說吧,C#的ThreadStatic是假的,因?yàn)镃#完全是由CLR(C++)承載的,言外之意C#的線程本地存儲,用的就是用C++運(yùn)行時(shí)提供的 __declspec(thread)__thread 來虛構(gòu)的一套玩法,這一篇我們就來簡單聊一聊。

二:C# 的線程本地存儲

1. 虛構(gòu)在哪里

在 C# 中使用ThreadStatic就可以將變量和線程進(jìn)行綁定,參考代碼如下:


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

        static void Main(string[] args)
        {
            Console.WriteLine($"num={num}");

            Debugger.Break();
        }
    }

在 CLR 中如何將 num 與 Thread 綁定呢?研究過 CLR 源碼的朋友應(yīng)該知道是用 ThreadLocalInfo 的,參考代碼如下:


#ifdef _MSC_VER
__declspec(selectany) __declspec(thread) ThreadLocalInfo gCurrentThreadInfo;
#else
EXTERN_C __thread ThreadLocalInfo gCurrentThreadInfo;
#endif

struct ThreadLocalInfo
{
    Thread* m_pThread;
    AppDomain* m_pAppDomain; // This field is read only by the SOS plugin to get the AppDomain
    void** m_EETlsData; // ClrTlsInfo::data
};

上面的 m_pThread 就是 C# Thread 在 CLR 層面的承載,怎么去驗(yàn)證呢?可以把代碼跑起來,然后用 windbg 驗(yàn)證一下。


0:000> dt coreclr!gCurrentThreadInfo
   +0x000 m_pThread        : 0x000001e3`506c5fa0 Thread
   +0x008 m_pAppDomain     : 0x000001e3`506ba9b0 AppDomain
   +0x010 m_EETlsData      : 0x000001e3`506aa360  -> (null) 

0:000> !t
ThreadCount:      3
UnstartedThread:  0
BackgroundThread: 2
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1     2e04 000001E3506C5FA0    2a020 Preemptive  000001E3521DCE80:000001E3521DD4A8 000001e3506ba9b0 -00001 MTA 
   6    2     4ef8 000001E3506F1A30    21220 Preemptive  0000000000000000:0000000000000000 000001e3506ba9b0 -00001 Ukn (Finalizer) 
   7    3     3550 000001E3726A0AE0    2b220 Preemptive  0000000000000000:0000000000000000 000001e3506ba9b0 -00001 MTA 

從卦中可以清楚的看到 m_pThread=0x000001e3506c5fa0 就是我們的主線程,最后的 num 就是放在與之關(guān)聯(lián)的 ThreadLocalModule 中,這個(gè)比較簡單,關(guān)注下匯編代碼就好了,下面的 rax 就是 ThreadLocalModule。


00007ffb`218d2c2c 48b9b07b9921fb7f0000 mov rcx,7FFB21997BB0h
00007ffb`218d2c36 ba04000000      mov     edx,4
00007ffb`218d2c3b e8001fb55f      call    coreclr!JIT_GetSharedNonGCThreadStaticBase (00007ffb`81424b40)
00007ffb`218d2c40 8b4820          mov     ecx,dword ptr [rax+20h]
00007ffb`218d2c43 894dfc          mov     dword ptr [rbp-4],ecx

0:000> dp rax+0x20 L1
00000294`d0539790  abababab`0000000a

CLR層面用了太多的高層虛構(gòu)來玩了一套線程本地存儲,其實(shí)最核心的還要理解再下一層的 __declspec(selectany) ,接下來聊聊這玩意是怎么玩的。

2. __declspec(selectany) 是怎么玩的

在Windows層面的術(shù)語中,有兩種 TLS 技術(shù)。

  • 動態(tài)TLS

借助 Windows 提供的 TlsAlloc, TlsSetValue 之類的方法來實(shí)現(xiàn),并且存放在線程 _TEB.TlsSlots 的槽位中,參考代碼如下:


0:000> dt 0x000000f4f0ca6000 ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   ...
   +0x1480 TlsSlots         : [64] (null) 
   ...

  • 靜態(tài)TLS

C#的線程本地存儲用的就是靜態(tài)TLS,也就是在編譯時(shí)就已經(jīng)聲明好的,在 PE 文件里面有一個(gè) .tls 節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)的數(shù)據(jù)會被每個(gè)線程在heap堆上copy一份,存放在 _TEB.ThreadLocalStoragePointer 來指向的指針數(shù)組中,參考代碼如下:


0:000> dt 0x000000f4f0ca6000 ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x058 ThreadLocalStoragePointer : 0x00000294`d0536ab0 Void
   ...

動態(tài)的TLS我就不介紹了,這里著重說一下靜態(tài)的TLS。

3. 靜態(tài)TLS詳解

為了方便講解,先上一段測試代碼。


#include <windows.h>
#include <stdio.h>
#include <limits.h>


__declspec(thread) int i = INT_MAX;
__declspec(thread) int j = INT_MAX;

int main() {
	int num1 = i;
	int num2 = j;
	printf("i=%d,j=%d", num1, num2);
}

上面的 i,j 值在編譯時(shí)就已經(jīng)放到了 PE 頭的 .tls 節(jié),可以用 PPEE 觀察下對象頭。

聊一聊 C# 的線程本地存儲TLS到底是什么

從卦中可以看到 .tls 占用了 0x400 字節(jié)大小,并且用 WinHex 真的觀察到了 i,j 的值,挺有意思。

在內(nèi)存中TLS區(qū)比這個(gè)還小一點(diǎn),可以觀察一下 DIRECTORY_ENTRY_TLS 節(jié)的 StartAddressOfRawData 和 EndAddressOfRawData 字段,這也是每個(gè)線程copy的原始內(nèi)存區(qū)域,可以看到只有 0x20D ,大概少了一半,截圖如下:

聊一聊 C# 的線程本地存儲TLS到底是什么

有了這些前置知識,接下來觀察內(nèi)存中的地址,在運(yùn)行之前先把 ASLR 關(guān)掉,匯編代碼參考如下:

   //int num1 = i;
   14 00411895 a1b4a14100      mov     eax,dword ptr [ConsoleApplication2!_tls_index (0041a1b4)]
   14 0041189a 648b0d2c000000  mov     ecx,dword ptr fs:[2Ch]
   14 004118a1 8b1481          mov     edx,dword ptr [ecx+eax*4]
   14 004118a4 8b8208010000    mov     eax,dword ptr [edx+108h]
   14 004118aa 8945f8          mov     dword ptr [ebp-8],eax

   //int num2 = j;
   15 004118ad a1b4a14100      mov     eax,dword ptr [ConsoleApplication2!_tls_index (0041a1b4)]
   15 004118b2 648b0d2c000000  mov     ecx,dword ptr fs:[2Ch]
   15 004118b9 8b1481          mov     edx,dword ptr [ecx+eax*4]
   15 004118bc 8b8204010000    mov     eax,dword ptr [edx+104h]
   15 004118c2 8945ec          mov     dword ptr [ebp-14h],eax

可以看到每一句大概會生成 5 行匯編代碼,我們簡單分析下。

  • ConsoleApplication2!_tls_index (0041a1b4)

這個(gè)值就是 PE 頭的 AddressOfIndex 值,可以再回頭觀察下,里面存的就是 tls 索引,當(dāng)前是 0 ,參考如下:


0:000> dp 0041a1b4 L1
0041a1b4  00000000

  • fs:[2Ch]

在用戶態(tài)層面上 fs 指向的是當(dāng)前線程的 TEB 結(jié)構(gòu),其中的 2C 偏移指的就是 ThreadLocalStoragePointer 結(jié)構(gòu),windbg 觀察如下:


0:000> dg fs
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0053 002bc000 00000fff Data RW Ac 3 Bg By P  Nl 000004f3

0:000> dt 0x002bc000 ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : (null) 
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : (null) 
   +0x02c ThreadLocalStoragePointer : 0x00664400 Void
   ...

  • edx,dword ptr [ecx+eax*4]

這句匯編是一個(gè)數(shù)組操作,翻譯成 C 就是 ThreadLocalStoragePointer[tls]。


0:000> dp 0x00664400 L1
00664400  00664448

這里要提醒的是:上面的 00664448 所在的 heap 位置其實(shí)就是 PE 頭里的 StartAddressOfRawData~EndAddressOfRawData內(nèi)存區(qū)域的 copy,截圖如下:

聊一聊 C# 的線程本地存儲TLS到底是什么

  • eax,dword ptr [edx+108h]

這句話的意思就是在 數(shù)組元素1 這個(gè)結(jié)構(gòu)上偏移108的位置存放著我們的 num 值,用 windbg 觀察之后果然就是的。


0:000> dp 00664448+0x108 L1
00664550  7fffffff

三:總結(jié)

C# 屬于一種業(yè)務(wù)高層抽象的語言,它的很多底層被C++再次隔離了,想要理解本篇的TLS,還得需要往下一層一層的擊穿,作為C#程序員太難了。文章來源地址http://www.zghlxwxcb.cn/news/detail-771853.html

聊一聊 C# 的線程本地存儲TLS到底是什么

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

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

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

相關(guān)文章

  • 聊一聊大模型

    事情還得從ChatGPT說起。 2022年12月OpenAI發(fā)布了自然語言生成模型ChatGPT,一個(gè)可以基于用戶輸入文本自動生成回答的人工智能體。它有著趕超人類的自然對話程度以及逆天的學(xué)識。一時(shí)間引爆了整個(gè)人工智能界,各大巨頭也紛紛跟進(jìn)發(fā)布了自家的大模型,如:百度-文心一言、科

    2024年02月05日
    瀏覽(32)
  • 聊一聊synchronized

    在 Java 中, synchronized 可以用于實(shí)現(xiàn)線程同步,有以下幾種常見的使用方式: 修飾代碼塊:將 synchronized 放在代碼塊的前面, 例如: 在這種方式下,會為給定的對象 obj 獲取鎖,在代碼塊執(zhí)行期間,只有持有該鎖的線程才能進(jìn)入代碼塊執(zhí)行。 修飾方法:將 sync

    2024年01月22日
    瀏覽(27)
  • 聊一聊AIGC

    聊一聊AIGC

    “UGC不存在了”——借鑒自《三體》 ChatGPT 的橫空出世將一個(gè)全新的概念推上風(fēng)口——AIGC( AI Generated Content)。 GC即創(chuàng)作內(nèi)容(Generated Content),和傳統(tǒng)的UGC、PGC,OGC不同的是,AIGC的創(chuàng)作主體由人變成了人工智能。 xGC PGC:Professionally Generated Content,專業(yè)生產(chǎn)內(nèi)容 UGC:User G

    2024年02月10日
    瀏覽(22)
  • 聊一聊模板方法模式

    聊一聊模板方法模式

    統(tǒng)一抽取,制定規(guī)范; 模板方法模式,又叫模板模式,屬于23種設(shè)計(jì)模式中的 行為型模式 。在抽象類中公開定義了執(zhí)行的方法,子類可以按需重寫其方法,但是要以抽象類中定義的方式調(diào)用方法??偨Y(jié)起來就是: 定義一個(gè)操作的算法結(jié)構(gòu),而將一些步驟延遲到子類中。在不

    2024年02月04日
    瀏覽(26)
  • 聊一聊Vue和Ts

    1 前言 Vue3 已經(jīng)正式發(fā)布了一段時(shí)間了,各種生態(tài)已經(jīng)成熟。最近使用 taro+vue3 重構(gòu)冷鏈的小程序,經(jīng)過了一段時(shí)間的開發(fā)和使用,有了一些自己的思考。 總的來說,Vue3 無論是在底層原理還是在實(shí)際開發(fā)過程中,都有了很大的進(jìn)步。 從源碼層面來說,使用 Proxy 代替 Object.d

    2023年04月08日
    瀏覽(26)
  • 聊一聊適配器模式

    聊一聊適配器模式

    接口不能用?行,我?guī)湍氵m配 適配器模式(Adapter),是23種設(shè)計(jì)模式中的 結(jié)構(gòu)型模式 之一;它就像我們電腦上接口不夠時(shí),需要用到的拓展塢,起到轉(zhuǎn)接的作用。它可以將新的功能和原先的功能連接起來,使由于需求變動導(dǎo)致不能用的功能,重新利用起來。 上圖的Mac上,只

    2024年02月04日
    瀏覽(24)
  • 聊一聊mysql中的間隙鎖

    聊一聊mysql中的間隙鎖

    間隙鎖在mysql中經(jīng)常使用到,今天就聊一聊mysql的間隙鎖的內(nèi)容。 間隙鎖是為了解決幻讀的問題,并且在當(dāng)前讀的場景下解決的。 當(dāng)前讀包含:update,delete,insert,select…lock in share mode,select…for update 一基本概念 1、行鎖:給某一行進(jìn)行加鎖 2、間隙鎖:兩個(gè)值之間的間隙,為解

    2024年02月12日
    瀏覽(21)
  • 聊一聊nginx中KeepAlive的設(shè)置

    聊一聊nginx中KeepAlive的設(shè)置

    之前工作中遇到一個(gè)KeepAlive的問題,現(xiàn)在把它記錄下來,場景是這樣的: 從上圖可以看出,用戶通過Client訪問的是LVS的VIP, VIP后端掛載的RealServer是Nginx服務(wù)器。 Client可以是瀏覽器也可以是一個(gè)客戶端程序。一般情況下, 這種架構(gòu)不會出現(xiàn)問題,但是如果Client端把請求發(fā)送給

    2024年02月01日
    瀏覽(27)
  • 聊一聊Java抽象同步隊(duì)列AQS

    聊一聊Java抽象同步隊(duì)列AQS

    AQS是鎖的底層支持 由該圖可以看到,AQS是一個(gè)FIFO的雙向隊(duì)列,其內(nèi)部通過節(jié)點(diǎn)head和tail記錄隊(duì)首和隊(duì)尾元素,隊(duì)列元素的類型為Node。其中Node中的thread變量用來存放進(jìn)入AQS隊(duì)列里面的線程;Node節(jié)點(diǎn)內(nèi)部的SHARED用來標(biāo)記該線程是獲取共享資源時(shí)被阻塞掛起后放入AQS隊(duì)列的,EX

    2024年02月16日
    瀏覽(33)
  • 聊一聊近期測試行情以及個(gè)人的感受

    聊一聊近期測試行情以及個(gè)人的感受

    眾所周知,去年年底的裁員潮再加上今年的疫情影響,失業(yè)、找工作成為了蠻多人的當(dāng)務(wù)之急。最近一些招聘網(wǎng)站也出現(xiàn)被刷爆的情況,其中順利找到工作的并不多,說明行情很冷,但是總有許多人順利跳槽。 其實(shí)對于大牛來說,行業(yè)是否景氣,影響真不大。反而有時(shí)候更容

    2024年02月09日
    瀏覽(15)

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

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包