主頁 > .NET開發 > dotnet 謹慎在靜態建構式里使用鎖

dotnet 謹慎在靜態建構式里使用鎖

2022-09-16 06:08:13 .NET開發

在 dotnet 的最佳實踐里面,不推薦在靜態建構式里面包含復雜的邏輯,其中也就包含了本文聊的和多執行緒相關的鎖的使用,最佳做法是盡量不要在靜態建構式里面碰到任何和鎖以及多執行緒安全相關的邏輯,本文來告訴大家,在靜態建構式里面使用鎖將帶來的問題以及原因

在 .NET 的設計里面,一個型別的靜態建構式,是在此型別第一次被碰到時將會被 CLR 呼叫,呼叫的時候,只允許一個執行緒執行進入靜態建構式,換句話說是一個型別的靜態建構式不會重復被多個執行緒執行,只會被執行一次,如此即可保證靜態建構式的安全性

不同于實體建構式,實體建構式大部分由代碼里面的 new 關鍵詞觸發,執行代碼的僅有一個執行緒,如果多個執行緒呼叫 new 關鍵詞那么將創建出來不同的實體,分別參考不同的記憶體空間,如以下代碼

var foo = new Foo();

如果有多個執行緒同時進入,呼叫到 new Foo() 這句代碼,自然是創建出多個不同的實體,這就意味著無論是靜態建構式還是實體建構式,都是只能被一個執行緒執行,當然,這是有例外的,由于在 .NET 里面,無論是靜態建構式還是實體建構式,都是一個函式方法,通過反射,依然可以當成基礎的方法呼叫,因此在使用反射時,以上的說法是不成立的

在不使用反射的黑科技下,保持讓建構式只能由一個執行緒執行,可以解決十分多的執行緒同步安全問題

對于實體的建構式只能由一個執行緒執行這個十分好理解,由于進入代碼里面,不同的執行緒將會創建出不同的物件,每個物件都有自己的獨立的記憶體空間,獨立的記憶體空間里面執行的實體建構式執行的程序引數以及欄位等都是獨立的,實際有兩個執行緒同時呼叫 new Foo() 代碼,兩個執行緒所使用的實體建構式也是不同的,例如建構式里面使用的程序引數 this.this 就分別屬于不同的兩個物件

然而靜態建構式就比較復雜起來的,大家都知道,在沒有標記執行緒靜態的前提下,所有的靜態欄位和屬性等都是全域共享的,全域共享的就意味著所有的執行緒都訪問到的相同的物件

如上文所說,一個型別的靜態建構式將在型別第一次被碰到時被 CLR 呼叫,那如何了解當前是第一次碰到?如果有兩個執行緒同時都碰到呢,此時由哪個執行緒執行,還是兩個執行緒都要執行?

在靜態建構式被多個執行緒碰到時,相當于進入了資源競爭,無論是多少個執行緒同時碰到某個型別,此型別的靜態建構式只能由其中的一個執行緒執行,而其他執行緒進入等待程序,相當于進入靜態建構式時設定了一個鎖物件,只有一個執行緒能進入呼叫靜態建構式,其他執行緒只能等待靜態建構式執行完成才能繼續

多執行緒在碰到某個型別的靜態建構式時,就和碰到競態資源一樣,也相當于碰到一個鎖

然而靜態建構式的多執行緒安全問題可比其他的競態資源更加復雜,原因也如上文描述,一個型別的靜態建構式是在這個型別第一次被碰到的時候觸發,然而代碼里面什么時候是第一次碰到,這個是非常復雜且不可控的,而且也會隨著代碼的迭代而被變更的,例如當前是十分確定有某個函式碰到了某個型別,然而很快就會因為函式之前的呼叫順序變更,從而變更了靜態建構式的初始化時機,或者在代碼迭代時,在新的時機更快碰到了某個型別,從而觸發了型別的靜態建構式

沒有開發者會在寫代碼的時候,想到碰到某個型別時,需要關注此型別的靜態建構式的初始化時機是否被更改,從而導致了問題,如果真的如此關注了,那代碼也寫不了了,碰到的每一個型別,都需要關注一下的話,這個開發就不好玩了

這就是為什么最佳實踐里面推薦不要在靜態建構式里面放復雜的邏輯,推薦只是做一些簡單的初始化邏輯,如此能很大解決因為靜態建構式的時機問題導致的問題,無論什么時候碰到靜態建構式,如果靜態建構式只是做非常簡單的和無依賴的邏輯,那自然是沒有什么問題

而如果是如本文要聊的,在型別的靜態建構式里面,碰到了鎖,那這個故事就開始復雜起來了

無論是什么語言,只要還是在圖靈的體系下,只要在玩多執行緒,那么鎖和原子和事務是少不了的,不過這是一個很大的話題,本文只來和大家聊鎖與靜態建構式,在使用鎖的時候,能帶來的優勢是提供了一個解決多執行緒安全問題的方法,帶來的問題是多執行緒安全問題,沒錯鎖是一個會導致的執行緒安全問題的解決多執行緒問題的方法,是否會導致問題,完全取決于如何使用,鎖不是一個完美的解決方案,如果使用不當,那帶來的執行緒安全問題將會有很多,而且鎖的使用注意點也非常多,這就是為什么會有本文的核心原因

在使用鎖的最佳實踐里面,就有確定性的說法,也就是說何時捕獲鎖、等待鎖,以及合適釋放鎖都應該是確定的,而不能是不確定的行為,否則輕的話就是執行緒不安全,資源被意外搶入,重的話就是無限執行緒互等,應用進入摸魚狀態,啥都不做都在等著鎖,或者應用拉滿了計算資源瘋狂執行

在靜態建構式里面使用鎖將違背鎖的最佳實踐里面的確定性呼叫這一條,靜態建構式是在型別第一次碰到時被觸發,也就是開發者是無法確定靜態建構式合適被呼叫的,再加上一些代碼優化和行內,將會導致除錯下和發布下的行為也會不同,再加上代碼迭代,靜態建構式的觸發時機也是很難進行控制的,在靜態建構式里面使用鎖將是一個危險的行為,即使當前版本在除錯下是能符合預期作業的,然而在發布的時候,在某些用戶的設備上,也許就會遇到奇怪的問題,如果想要提升產品的代碼質量,就需要盡量不要在靜態建構式里面使用鎖的相關方法,包括直接或間接的呼叫到鎖

舉一個例子來告訴大家在靜態建構式里面呼叫鎖的相關方法導致的多執行緒互等的問題

假設在 Foo 型別的靜態建構式里面需要使用到一個叫 LockObject 物件的鎖,而這個 LockObject 物件的鎖是有多個型別在呼叫的,定義代碼如下

class Foo2
{
    public static void Do(Action action)
    {
        lock (LockObject)
        {
            action();
        }
    }

    public static readonly object LockObject = new object();
}

此時有 Foo1 型別,在靜態建構式呼叫了 Foo2 的 Do 方法,代碼如下

class Foo1
{
    static Foo1()
    {
        Foo2.Do(() =>
        {
            // 忽略代碼
            Number = 0;
        });
    }

    public static int Number { get; private set; }
}

以上代碼在 Foo1 被第一次碰到的程序中,可能會存在多執行緒相互等待,例如呼叫代碼如下

        var task1 = Task.Run(() =>
        {
            Foo2.Do(() =>
            {
                Thread.Sleep(2000);
                GetFoo1Number();
            });
        });

        var task2 = Task.Run(() =>
        {
            GetFoo1Number();
        });

    private static int GetFoo1Number()
    {
        return Foo1.Number;
    }

運行代碼可以看到 task1 和 task2 在互等,點擊暫停,可以看到 task1 和 task2 對應的執行緒的執行緒號分別是 9764 和 22044 兩個,其呼叫堆疊分別如下

執行緒號是 9764 的 task1 的呼叫堆疊如下

>	Demo.dll!Demo.Foo1.Number.get() 行 67	C#
 	Demo.dll!Demo.MainWindow.GetFoo1Number() 行 51	C#
 	Demo.dll!Demo.MainWindow..ctor.AnonymousMethod__0_2() 行 35	C#
 	Demo.dll!Demo.Foo2.Do(System.Action action) 行 76	C#

執行緒號是 22044 的 task2 的呼叫堆疊如下

 	[正在等待執行緒 鎖定 擁有的 9764,雙擊或按 Enter 可切換到執行緒]	
 	System.Private.CoreLib.dll!System.Threading.Monitor.Enter(object obj, ref bool lockTaken)	未知
>	Demo.dll!Demo.Foo2.Do(System.Action action) 行 74	C#
 	Demo.dll!Demo.Foo1.Foo1() 行 60	C#
 	[本機到托管的轉換]	
 	[托管到本機的轉換]	
 	Demo.dll!Demo.Foo1.Number.get() 行 67	C#

也就是說 task1 在嘗試拿到 Foo1 的 Number 屬性,需要先等待 Foo1 的靜態建構式執行完成,然而 Foo1 的靜態建構式是在 task2 對應的執行緒執行,而 Foo1 的靜態建構式碰到的 Foo2 的 LockObject 物件的鎖被 task1 對應的執行緒獲取,因此想要讓 Foo1 的靜態建構式能繼續執行,就需要等待 task1 執行緒釋放鎖物件,然而 task1 要釋放鎖物件的前提是能獲取完成 Foo1 的 Number 屬性,但是獲取 Foo1 的 Number 屬性需要等待在 task2 上執行的 Foo1 的靜態建構式執行完成

也就是說在 task1 上執行的代碼,需要等待 task2 執行完成,才能釋放鎖,在 task2 上執行的代碼,需要等待 task1 釋放鎖才能執行完成,完美讓兩個執行緒進入互等

這就是其中的一個執行緒不安全的例子,如果將 task1 里面的 Thread.Sleep 去掉,那才是可怕,因為運行代碼,將會發現有時存在執行緒互等,有時不存在,如果這是發給用戶端執行的應用,那將會有用戶反饋說為什么有時候應用就啥也不干了,但有時又跑得好好的,說不定這時客服小姐姐的重啟搞定一切的大法就能解決這個問題,但是如果剛好是大佬用戶遇到了,要求開發者一定要解決,那預計開發者想要復現這個問題,也是很不好玩的,如果進入以上方法的步驟比較多,那大概可以連續多加幾天的班,如果再加上邏輯稍微復雜,加班的時候自己不清醒,那預計還是解決不了的

保持靜態建構式的簡單,可以解決大量的問題,不要在靜態建構式里面添加復雜的代碼,如果真的有這個需求,將這些復雜的代碼放在一個靜態函數里面,自己尋找合適的時機呼叫

博客園博客只做備份,博客發布就不再更新,如果想看最新博客,請到 https://blog.lindexi.com/

知識共享許可協議
本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可,歡迎轉載、使用、重新發布,但務必保留文章署名[林德熙](http://blog.csdn.net/lindexi_gd)(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用于商業目的,基于本文修改后的作品務必以相同的許可發布,如有任何疑問,請與我[聯系](mailto:[email protected]),

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/508703.html

標籤:.NET技术

上一篇:dotnet 讀 WPF 源代碼筆記 WPF 是如何做到一套代碼兼容多個 .NET Framework 版本

下一篇:Microsoft.Maui.Graphics.Skia 使用 DrawString 繪制文本的坐標問題

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more