天天看點

dotnet C# 給結構體字段指派非線程安全

在 dotnet 運作時中,給引用對象進行指派替換的時候,是線程安全的。給結構體對象指派,如果此結構體是某個類的成員字段,那麼此指派不一定是線程安全的。是否線程安全,取決于結構體的大小,取決于此結構體能否在一次原子指派内完成

大家都知道,某個執行邏輯如果是原子邏輯,那麼此邏輯是線程安全的。原子邏輯就是一個非 A 即 B 的狀态的變更,絕對不會存在處于 A 和 B 中間的狀态。滿足于此即可稱為線程安全,因為線程不會讀取到中間狀态。在 dotnet 運作時裡面,特别對了引用對象,也就是類對象的指派過程進行了優化,可以讓對象的指派是原子的

從運作時的邏輯上,可以了解到的是引用對象的指派本質上就是将新對象的引用位址指派,對象引用位址可以認為是指針。在單次 CPU 運算中可以一次性完成,不會存在隻寫入某幾位而還有某幾位沒有寫入的情況

dotnet C# 給結構體字段指派非線程安全

大概可以認為在 x86 上,單次的原子指派長度就是 32 位。這也就是為什麼 dotnet 裡面的對象位址設計為 32 位的原因

但是對于結構體來說,需要分為兩個情況,定義在棧上的結構體,如某個方法的局部變量或參數是結構體,此時的結構體是存放在棧上的,而在 dotnet 裡面,每個線程都有自己獨立的棧,是以放在棧上的結構體線上程上是獨立的,互相之間沒有影響,也就是線程安全的

如果是放在堆上面的結構體,如作為某個類對象的字段,此時的結構體将會占用此類對象的記憶體空間,如對以下代碼的記憶體示意圖

此時的 Foo 對象在記憶體上的布局示意圖大概如下

dotnet C# 給結構體字段指派非線程安全

如上面示意圖,在記憶體布局上,将會在類記憶體布局上将結構體展開,占用類的一段記憶體空間。也就是說本質上結構體如命名,就是多個基礎類型的組合,實際上是運作的概念。也就是說在給類對象的字段是結構體進行指派的時候,每次指派的内容僅僅是取決于原子長度,如 x86 下使用 32 位進行指派,相當于先給 FooStruct 的 A 進行指派,再給 FooStruct 的 B 進行指派等等。此時如果有某個線程在進行指派,某個線程在進行讀取 Foo 對象的 FooStruct 字段,那麼也許讀取的線程會讀取到正在指派到一半的 FooStruct 結構體

如以下的測試代碼

以上代碼開啟了很多線程,每個線程都在嘗試讀寫此結構體。每次寫入的指派都是在 A B C D 給定相同的一個數值,在讀取的時候判斷是否讀取到的每一個屬性是否都是相同的數值,如果存在不同的,那麼證明給結構體指派是線程不安全的

運作以上代碼,可以看到,在結構體中,存在屬性的數值是不相同的。通過以上代碼可以看到,放在類對象的字段的結構體,進行指派是線程不安全的

本文所有代碼放在github 和 gitee 歡迎通路

可以通過如下方式擷取本文的源代碼,先建立一個空檔案夾,接着使用指令行 cd 指令進入此空檔案夾,在指令行裡面輸入以下代碼,即可擷取到本文的代碼

以上使用的是 gitee 的源,如果 gitee 不能通路,請替換為 github 的源

擷取代碼之後,進入 YanibeyeNelahallfaihair 檔案夾

dotnet C# 給結構體字段指派非線程安全

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協定進行許可。歡迎轉載、使用、重新釋出,但務必保留文章署名林德熙(包含連結: ),不得用于商業目的,基于本文修改後的作品務必以相同的許可釋出。如有任何疑問