日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

CLR线程概览(一)

發布時間:2023/12/10 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 CLR线程概览(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

托管 vs. 原生線程

托管代碼在“托管線程”上執行,(托管線程)與操作系統提供的原生線程不同。原生線程是在物理機器上執行的原生代碼序列;而托管線程則是在CLR虛擬機上執行的虛擬線程。

正如JIT解釋器將“虛擬的”中間(IL)指令映射到物理機器上的原聲指令,CLR線程基礎架構將“虛擬的”托管線程映射到操作系統的原生線程上。

在任意時刻,一個托管線程可能會也可能不會被分配到一個原生線程執行。例如,一個已經被創建(通過“new System.Threading.Thread”)但是未啟動(通過“System.Threading.Thread.Start”)的托管線程不會被指派到原生線程上執行。類似的,雖然CLR在實際上不會這樣做,但是一個托管線程在執行時可被切換到多個原生線程上執行。

托管代碼里公開的Thread接口就是用來隱藏其底層原生線程的細節的:

  • 托管線程無需綁定到一個原生線程上(甚至有可能根本不映射到原生線程上)。
  • 不同操作系統的原生線程不一樣。
  • 原則上,托管線程是“虛擬的”。

CLR提供并實現了托管線程的抽象。比如說,雖然其不暴露操作系統的線程本地存儲(TLS)機制,但是其提供了托管“線程靜態”變量。類似的,雖然其不提供原生線程的“線程ID”,但是其提供與操作系統無關的“托管線程ID”。不過為了便于診斷問題,底層原生線程的一些細節可以通過System.Diagnostics命名空間里的類型獲得。

托管線程還提供了原生線程通常不用的功能。第一,托管線程在堆棧上使用GC引用,這樣CLR必須在GC的時候可以枚舉(甚至可能修改)這些GC引用。為了實現這個目的,CLR必須“暫停”每個托管線程(即停止執行以便可以發現所有的GC引用)。第二,當AppDomain卸載時,CLR必須保證沒有線程在執行這個AppDomain里的代碼。這要求CLR可以強制線程從AppDomain脫離,CLR通過在線程里注入ThreadAbortException來實現這點。

數據結構

每個托管線程都跟一個Thread對象關聯,其在threads.h里定義。這個對象跟蹤CLR關于托管對象所需要了解的所有東西。包括如線程的當前GC模式和堆棧幀鏈這些必需品,也包括為了性能因素創建的很多元素(如一些快速arena-style分配器)。

所有的Thread對象都保存在ThreadStore中(也在threads.h中定義),其時一個所有已知線程的列表。要遍歷所有的托管線程,需要先獲取ThreadStoreLock,再使用ThreadStore::GetAllThreadList來枚舉所有的線程對象。這個列表也包含沒有被指派原生線程的托管線程(如未啟動的線程,或原生線程已經存在了)。

原生線程可以通過一個原生線程本地存儲(TLS)槽來獲取綁定到該原生線程的托管線程。這允許原生線程上運行的代碼可以通過GetThread()獲取對應的Thread對象。

另外,許多托管線程有一個與原生Thread對象相區別的?托管?Thread對象(System.Threading.Thread)。托管Thread對象提供了方法以便托管代碼與線程交互,其大部分是原生Thread對象功能的封裝。通過Thread.CurrentThread可以(在托管代碼中)獲取到當前的托管線程對象。

在調試器里,“!Threads”這個SOS擴展命令可以用來枚舉ThreadStore里的所有Thread對象。

線程的生命周期

一個托管線程在下列這些情形中創建:

  • 托管代碼通過System.Threading.Thread顯式要求CLR創建一個新線程。
  • CLR自己創建的托管線程(參見“特殊線程”一節)。
  • 原生代碼在原生線程上調用托管代碼,而這個托管代碼沒有跟托管線程相關聯(通過“反向p/invoke”或者COM互交互)。
  • 一個托管進程被啟動了(在進程的主線程上調用其Main函數)。
  • 在#1和#2這些情形中,CLR負責創建支撐托管線程的原生線程。這個只會在線程實際上啟動了才會發生。在這些情形里,CLR“負責”原生線程;CLR負責原生線程的生命周期,由于CLR創建了它,因此也就知道線程的存在。

    在#3和#4這些情形里,原生線程在托管線程之前就存在了,而且由CLR之外的代碼負責。CLR不負責這種原生線程的生命周期。CLR只是在其第一次調用托管代碼時意識到其存在。

    當一個原生線程結束時,CLR通過其DllMain函數獲得通知。這在操作系統的“加載鎖”中發生,所以在處理這個通知的時候只能做很少(安全)的事情。與其銷毀與托管線程關聯的數據結構,這個線程只是被簡單地標識成“死亡”狀態,并啟動finalizer線程。finalizer線程會遍歷ThreadStore里所有死亡且托管代碼不再使用的線程。

    暫停

    CLR必須可以找到托管對象的所有引用以便執行GC。托管代碼一直在不停的訪問GC堆,操作堆棧和寄存器上的引用。CLR必須保證所有線程停在安全可靠的位置(這樣他們不會修改GC堆),以便找到所有的托管對象。它只會停在安全點,這個時候可以在寄存器和堆棧上檢查所有可用的引用。

    另一個辦法就是GC堆、每個線程的堆棧和寄存器狀態都是所謂的“共享狀態”,可被多個線程訪問。正如大多數共享狀態一樣,需要一些“鎖”來保護它們。托管代碼在訪問堆之前必須要獲取鎖,并且在安全的時候釋放鎖。

    CLR將這種“鎖”稱作線程的“GC模式”。當線程獲取鎖的時候,處于“合作模式(cooperative mode)”;其必須與GC“合作”(通過釋放鎖)才能允許進行垃圾回收。而線程沒有獲取鎖的時候,處于“優先模式(preemptive mode)” - GC可以“優先”進行垃圾回收,因為其知道線程沒有訪問GC堆。

    GC只有在所有線程都處于“優先”模式(即沒有獲取鎖)時才能進行垃圾回收。將所有線程移到優先模式的過程就稱為“GC懸停(GC suspension)”或“暫停執行引擎”。

    一個不大成熟的實現“鎖”的方案是要求每個托管線程在訪問GC堆的時候實際獲取和釋放保護它的鎖。然后GC會向每個線程嘗試獲取鎖,一旦其獲取所有線程的鎖,就可以安全的進行垃圾回收了。

    然而,上面的方案因為兩個原因而顯得不足。第一,這會要求托管代碼耗費大量的時間在于獲取和釋放鎖(或至少是檢查GC是否在嘗試獲取鎖 - 也就是“GC輪詢 GC poll - 即不停的向GC輪詢”)。第二,它要求JIT解釋器生成大量的“GC信息代碼”,以描述每一行JIT生成的代碼后的堆棧的布局和寄存器狀態,這些信息會耗費大量的內存。

    我們針對上述辦法的改進方案是,將JIT后的托管代碼區分成“部分可中斷”和“全部可中斷”的代碼。在部分可中斷代碼中,調用其他函數的地方是唯一的安全點,且JIT生成顯式的“GC輪詢”點以便檢查是否有等待的GC。(JIT)只需要在這些地方生成GC信息。在全部可中斷代碼里,每個指令都是一個安全點,JIT為每個指令生成GC信息 - 但是其不生成“GC”輪詢代碼。全部可中斷代碼而是通過劫持線程(該過程在后文講解)來進入“中斷”狀態。JIT基于代碼質量,GC信息的大小以及GC懸停的時間延遲這些因素來判定是產生全部或部分可中斷代碼。

    基于上述信息,定義了三個基礎操作:進入合作模式,離開合作模式以及暫停執行引擎。

    進入合作模式

    一個線程通過調用Thread::DisablePreemptiveGC進入合作模式。其為當前線程獲取“鎖”:

  • 如果有GC正在執行(GC擁有這個鎖),那么等待GC完成。
  • 標識這個線程將進入合作模式,在這個線程進入“優先模式”之前不能觸發GC。
  • 兩個步驟實際上是原子操作。

    進入優先模式

    一個線程通過調用Thread::EnablePreemptiveGC來進入優先模式(釋放鎖)。其通過標識線程不再進入合作模式來完成,并通知GC線程可以啟動執行。

    中斷執行引擎

    當GC開始運行時,第一步就是中斷執行引擎。GCHeap::SuspendEE函數就是用來干這個的:

  • 設置一個全局變量(g_fTrapReturningThreads)來標志GC正在執行,任何想進入合作模式的線程都會被阻止,直到GC運行完畢。
  • 找出所有處于合作模式的線程,針對每個這樣的線程,試圖劫持線程并強制其離開合作模式。
  • 重復前面的步驟直到沒有線程處于合作模式。
  • 劫持

    為了GC懸停而進行的劫持操作是通過Thread::SysSuspendForGC函數完成的。這個函數通過強制所有運行在合作模式的托管線程在“安全點”離開合作模式。其通過枚舉所有的托管線程(通過遍歷ThreadStore),針對每個運行在合作模式中的托管線程:

  • 通過Win32的SuspendThread API來暫停底層的原生線程。這個API強制線程從運行狀態停止在任意位置(不一定是一個安全點)。
  • 通過GetThreadContext獲取線程的上下文(CONTEXT)。這是一個操作系統的概念;上下文存放了線程的當前寄存器狀態。這就允許我們來監視其指令寄存器,并獲知正在運行的指令類型。
  • 再次檢查線程是否在合作模式,因為其可能在被暫停之前已經離開合作模式了。如果是這樣的話,那么線程處于危險地段:線程可能在運行任意的原生代碼,必須立即恢復執行以規避死鎖。
  • 檢查線程是否在運行托管代碼。其有可能在合作模式下運行虛擬機(VM)自身的原生代碼(參看下面的同步章節),其也需要跟上一步一樣立即恢復執行。
  • 那么線程目前是暫停在托管代碼上。取決于代碼是全部還是部分可中斷,采取下面的措施之一:
    • 如果是全部可中斷,那么在任意位置GC都是安全的,因為線程按照全部可中斷的定義就是在安全點。理論上可以讓線程停在這個位置(因為是安全的),但是幾個歷史性的操作系統Bug妨礙了這點,因為前面獲取的線程上下文也許已經損壞了)。于是(CLR)改寫線程的指令寄存器,引導線程跳轉到一個代碼塊以便獲取更完整的上下文,離開合作模式,等待GC運行完畢,重新進入合作模式,并且還原線程的寄存器。
    • 如果是部分可中斷,那么線程按照定義不在一個安全點。但是,其調用者是處于安全點的(函數間切換)。基于這個知識,CLR在堆棧幀上“劫持”起返回地址(即修改堆棧),引導線程跳轉到跟“全部可中斷”類似的代碼塊。當函數返回時,其不是返回原來的調用函數那里,而是這個代碼塊(這個函數可能也會執行JIT在之前注入的GC輪詢,導致線程離開合作模式并撤銷劫持操作)。
  • ThreadAbort / AppDomain-Unload

    為了卸載一個應用程序域(AppDomain),CLR需要保證沒有線程運行在這個應用程序域中。為了實現這點,所有托管線程都被枚舉,而任何堆棧上有屬于被卸載應用程序域的幀的線程都被“中斷”。一個ThreadAbortException異常被注入正在運行的線程,并導致線程向上展開(一直運行拆除代碼)直到沒有運行在這個應用程序域當中的堆棧幀,而ThreadAbortException也被轉換成一個AppDomainUnloaded異常。

    ThreadAbortException是一個很特別的異常。其也許會被用戶代碼捕捉到,但是CLR確保其在用戶的異常處理代碼之后再次被拋出。因此ThreadAbortException有時被稱作“無法被捕捉”的,盡管嚴格來說不是這樣的。

    ThreadAbortException通常通過在托管線程上設置一個標志位標志其“正在終止”來拋出的。CLR很多地方都會檢查這個標志位(特別要注意的,每次從p/invoke返回),并且經常有設置這個標志位的目的就是為了讓線程及時終止的情形。

    然而,比如說,線程正在運行一個長時間的托管循環,那么它可能根本不會檢查這個標志位。為了讓這樣的線程快速終止,線程就被“劫持”并強制拋出ThreadAbortException異常。劫持過程跟GC懸停很類似,只是線程跳轉過去的代碼塊拋出ThreadAbortException,而不是等待GC運行完畢。

    這種劫持意味著ThreadAbortException可能在任意位置發生。這樣使得托管代碼很難正確處理ThreadAbortException異常。因此除了在卸載應用程序域的時候使用這種機制以外 - 保證由ThreadAbort損壞的狀態都跟應用程序域一起被清理,在其他地方使用它都不是很明智的選擇。

    轉載于:https://www.cnblogs.com/vowei/p/5460203.html

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的CLR线程概览(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。