Safe locks for multi-thread applications(多線程應用程式的安全鎖)

来源:https://www.cnblogs.com/hieroly/p/18215656
-Advertisement-
Play Games

介紹 在學習了sylar的C++高性能分散式伺服器框架後,想把自己在學習過程中的感想記錄下來。當然主要原因還是sylar的B站視頻過於難以理解了,也是想加強一下自己對這個框架的理解。很多內容也是借鑒了其他大佬的博文,比如找人找不到北,zhongluqiang 日誌模塊概述 日誌模塊的目的: 用於格式 ...


Safe locks for multi-thread applications(多線程應用程式的安全鎖)

由AB4327-GANDI,2016年1月9日。永久鏈接

開源mORMot框架

一旦你的應用程式是多線程的,就應該保護併發數據訪問。我們已經寫過關於調試多線程應用程式可能很困難的文章。

否則,可能會出現“競態條件”問題:例如,如果兩個線程同時修改一個變數(例如減少計數器),值可能會變得不一致且不安全。邏輯錯誤的另一個癥狀是“死鎖”,當兩個線程錯誤地使用鎖時,會導致整個應用程式似乎被阻塞且無響應,從而相互阻塞。

在預期24/7運行且無需維護的伺服器系統上,應避免此類問題。

在Delphi中,資源(可能是一個對象或任何變數)的保護通常通過臨界區來實現。

臨界區是一個對象,用於確保代碼的一部分一次只能由一個線程執行。臨界區需要在使用之前創建/初始化,併在不再需要時釋放。然後,一些代碼通過使用Enter/Leave方法進行保護,這將鎖定其執行:實際上,只有一個線程會擁有臨界區,所以只有一個線程能夠執行這段代碼,其他線程將等待直到鎖被釋放。為了獲得最佳性能,受保護的區域應儘可能小——否則,使用線程的好處可能會失效,因為任何其他線程都會等待擁有臨界區的線程釋放鎖。

我們現在將看到Delphi的 TCriticalSection可能存在的問題,以及我們的框架提出簡化臨界區在您的應用程式中的使用。

:在Delphi中,TCriticalSection 是用於管理線程同步的一個類。當多個線程需要訪問共用資源時,可以使用 TCriticalSection 來確保每次只有一個線程可以訪問該資源,從而防止數據競爭和不一致。然而,TCriticalSection 的使用也可能帶來一些問題,比如死鎖或者性能瓶頸,因此需要謹慎使用。mORMot框架提供了一些工具和策略來簡化 TCriticalSection 的使用,並幫助開發者更安全、更有效地管理線程同步。

修複 TRTLCriticalSection

在實踐中,您可能會使用一個 TCriticalSection類,或者更低級別的 TRTLCriticalSection記錄,後者可能是更好的選擇,因為它使用的記憶體更少,並且可以很容易地作為任何 class定義的(受保護)欄位包含進去。

假設我們要保護對變數a和b的任何訪問。以下是如何使用臨界區方法來實現:

var CS: TRTLCriticalSection;
    a, b: integer;
// 線上程開始前設置
InitializeCriticalSection(CS);
// 在每個TThread.Execute中:
EnterCriticalSection(CS);
try // 通過try...finally塊保護鎖
  // 從現在開始,您可以安全地更改變數
  inc(a);
  inc(b);
finally
  // 安全塊結束
  LeaveCriticalSection(CS);
end;
// 當線程停止時
DeleteCriticalSection(CS);

在最新版本的Delphi中,您可以使用 TMonitor類,它允許任何Delphi TObject擁有鎖。

在XE5之前,存在一些性能問題,即使到現在,這個受Java啟發的特性可能也不是最佳方法,因為它與單個對象綁定,並且與較舊版本的Delphi(或FPC)不相容。

幾年前,Eric Grange報告說——參見這篇博客文章——TRTLCriticalSection(連同 TMonitor)存在嚴重的設計缺陷,進入/離開不同的臨界區可能會使您的線程式列化,甚至整個性能可能比線程被序列化時更差。這是因為它是一個小的、動態分配的對象,所以幾個 TRTLCriticalSection的記憶體可能最終會落在同一個CPU緩存行中,當發生這種情況時,運行線程的核心之間會發生大量的緩存衝突。

Eric提出的修複方法非常簡單:

type
   TFixedCriticalSection = class(TCriticalSection)
   private
     FDummy: array [0..95] of Byte;
   end;

從T*Locked繼承

在定義您自己的類時,您可以繼承一些提供 TSynLocker實例的類,如在 SynCommons.pas中定義的:

  TSynPersistentLocked = class(TSynPersistent)
  ...
    property Safe: TSynLocker read fSafe;
  end;
  TInterfacedObjectLocked = class(TInterfacedObjectWithCustomCreate)
  ...
    property Safe: TSynLocker read fSafe;
  end;
  TObjectListLocked = class(TObjectList)
  ...
    property Safe: TSynLocker read fSafe;
  end;
  TRawUTF8ListHashedLocked = class(TRawUTF8ListHashed)
  ...
    property Safe: TSynLocker read fSafe;
  end;

所有這些類都將在其 constructor/destructor中初始化和終結它們所擁有的 Safe實例。

因此,我們可以這樣編寫我們的類:

type
  TMyClass = class(TSynPersistentLocked)
  protected
    fField: integer;
  public
    procedure UseLockUnlock;
    procedure UseProtectMethod;
  end;
{ TMyClass }
procedure TMyClass.UseLockUnlock;
begin
  fSafe.Lock;
  try
    // 現在我們可以安全地從多個線程訪問任何受保護的欄位
    inc(fField);
  finally
    fSafe.UnLock;
  end;
end;
procedure TMyClass.UseProtectMethod;
begin
  fSafe.ProtectMethod; // 調用fSafe.Lock並返回IUnknown本地實例
  // 現在我們可以安全地從多個線程訪問任何受保護的欄位
  inc(fField);
  // 當IUnknown被釋放時,將調用fSafe.UnLock
end;

如您所見,Safe: TSynLocker實例將在 TSynPersistentLocked父級定義並處理。

註入IAutoLocker實例

如果您的類繼承自 TInjectableObject,您甚至可以定義以下內容:

type
  TMyClass = class(TInjectableObject)
  private
    fLock: IAutoLocker;
    fField: integer;
  public
    function FieldValue: integer;
  published
    property Lock: IAutoLocker read fLock write fLock;
  end;
{ TMyClass }
function TMyClass.FieldValue: integer;
begin
  Lock.ProtectMethod;
  result := fField;
  inc(fField);
end;
var c: TMyClass;
begin
  c := TMyClass.CreateInjected([],[],[]);
  Assert(c.FieldValue=0);
  Assert(c.FieldValue=1);
  c.Free;
end;

在這裡,我們使用了依賴解析——請參閱[依賴註入和介面解析](http://synopse.info/files/html/Synopse mORMot Framework SAD 1.18.html#TITL_161)——讓 TMyClass.CreateInjected構造函數掃描其 published屬性,從而搜索 IAutoLocker的提供者。由於 IAutoLocker已全局註冊為通過 TAutoLocker解析,因此我們的類將使用新實例初始化其 fLock欄位。現在,我們可以像往常一樣使用 Lock.ProtectMethod來訪問關聯的 TSynLocker臨界區。

當然,這可能會比手動處理 TSynLocker更複雜,但是如果您正在編寫一個基於介面的服務,您的類可以從 TInjectableObject繼承以進行自身的依賴解析,因此這個技巧可能非常方便。

TSynLocker中的安全鎖定存儲

當我們解決了潛在的CPU緩存行問題時,您還記得我們在 TSynLocker定義中添加了一個填充二進位緩衝區嗎?由於我們不想浪費資源,TSynLocker提供了對其內部數據的輕鬆訪問,並允許直接處理這些值。由於它存儲為7個 variant值插槽,因此您可以存儲任何類型的數據,包括複雜的 TDocVariant文檔或數組。

我們的類可以使用此功能,並將其整數欄位值存儲在內部插槽0中:

type
  TMyClass = class(TSynPersistentLocked)
  public
    procedure UseInternalIncrement;
    function FieldValue: integer;
  end;
{ TMyClass }
function TMyClass.FieldValue: integer;
begin // 值的讀取也將受到互斥鎖的保護
  result := fSafe.LockedInt64[0];
end;
procedure TMyClass.UseInternalIncrement;
begin // 這個專用的方法將確保原子增加
  fSafe.LockedInt64Increment(0,1);
end;

請註意,我們使用了 TSynLocker.LockedInt64Increment()方法,因為以下方式是不安全的:

procedure TMyClass.UseInternalIncrement;
begin
  fSafe.LockedInt64[0] := fSafe.LockedInt64[0]+1;
end;

在上面的代碼中,獲取了兩個鎖(每個 LockedInt64屬性調用一個),因此另一個線程可能會在兩者之間修改值,並且增量可能不如預期準確。

TSynLocker提供了一些專用的屬性和方法來處理這種安全的存儲。這些期望一個 Index值,範圍從 0..6

    property Locked[Index: integer]: Variant read GetVariant write SetVariant;
    property LockedInt64[Index: integer]: Int64 read GetInt64 write SetInt64;
    property LockedPointer[Index: integer]: Pointer read GetPointer write SetPointer;
    property LockedUTF8[Index: integer]: RawUTF8 read GetUTF8 write SetUTF8;
    function LockedInt64Increment(Index: integer; const Increment: Int64): Int64;
    function LockedExchange(Index: integer; const Value: variant): variant;
    function LockedPointerExchange(Index: integer; Value: pointer): pointer;

如果有必要,您可以存儲一個 pointer或對 TObject實例的引用。

在我們的框架中,提供這樣一套線程安全的方法是有意義的,該框架提供了多線程伺服器能力——請參閱線程安全性

請隨時在mORMot文檔上繼續閱讀,其中可能包含有關此主題的更新和附加信息。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 二項分佈是描述固定次數獨立試驗中成功次數的概率分佈,常用於分析二元結果的事件,如拋硬幣。分佈由參數 n(試驗次數)、p(單次成功概率)和 k(成功次數)定義。概率質量函數 P(k) = C(n, k) * p^k * (1 - p)^(n - k)。NumPy 的 `random.binomial(... ...
  • Spring AI 初學 Spring AI 官方地址 ”spring 不生產 AI,只是 AI 工具的搬運工“ 項目可以查看gitee Open AI 前期準備 Open AI官方地址,需要使用魔法才能打開,同時購買很麻煩,建議淘寶進行購買,只需要購買 open ai 的 apikey 即可。 a ...
  • 在財稅工作中,處理髮票信息是一項繁瑣而重要的任務。然而,藉助先進的技術,我們可以將這個過程簡化並提高效率。今天,我將介紹一個API介面,它可以在秒級內識別發票信息,讓財稅工作變得更輕鬆。 這個API介面是由挖數平臺提供的,你可以在他們的網站上找到詳細的信息。它支持對多種類型的發票進行結構化識別,包括 ...
  • 一、爬取目標 小紅書是眾多客戶的流量藍海,可通過評論區數據高效引流獲客。我用python開發的爬蟲採集軟體,可自動抓取小紅書評論數據,並且含二級評論數據。 為什麼有了源碼還開發界面軟體呢?方便不懂編程代碼的小白用戶使用,無需安裝python,無需改代碼,雙擊打開即用! 1.1 效果截圖 軟體界面截圖 ...
  • 作者:l拉不拉米 鏈接:https://juejin.cn/post/7031445206152577061 一、前言 公司剛入職了一名中級Java開發,經過一個星期的適應學習,各方面表現還不錯,於是分配了一個小的迭代給新人做。 需求很簡單,把從第三方拉取的數據匹配到自身公司後臺設置的渠道後,聚合到 ...
  • 目的:求多個集合之前的並集,例如:現有四個集合C1 = {11, 22, 13, 14}、C2 = {11, 32, 23, 14, 35}、C3 = {11, 22, 38}、C4 = {11, 22, 33, 14, 55, 66},則它們之間的並集應該為: C1 & C2 & C3 = {11 ...
  • 1.排序方式 假設有一個序列,數據為:['n1', 'n2', 'n10', 'n11', 'n21', 'n3', 'n13', 'n20', 'n23'], 排序後需要達到這個效果:['n1', 'n2', 'n3', 'n10', 'n11', 'n13', 'n20', 'n21', 'n2 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...