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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...