.NET進階篇06-async非同步、thread多線程4

来源:https://www.cnblogs.com/xibei/archive/2019/12/07/12001805.html
-Advertisement-
Play Games

知識需要不斷積累、總結和沉澱,思考和寫作是成長的催化劑 梯子 一、鎖1、lock2、Interlocked3、Monitor4、SpinLock5、Mutex6、Semaphore7、Events1、AutoResetEvent2、ManualResetEvent3、ManualResetEvent ...


知識需要不斷積累、總結和沉澱,思考和寫作是成長的催化劑

梯子

一、鎖1、lock2、Interlocked3、Monitor4、SpinLock5、Mutex6、Semaphore7、Events1、AutoResetEvent2、ManualResetEvent3、ManualResetEventSlim8、ReaderWriterLock二、線程安全集合三、多線程模型1、同步編程模型SPM2、非同步編程模型APM3、基於事件編程模型EAP4、基於任務編程模型TAP四、End

一、鎖

資料庫中也有鎖概念,行鎖,表鎖,事物鎖等,鎖的作用就是控制併發情況下數據的安全一致,使一個數據被操作時,其他併發線程等待。開發方面多線程並行編程訪問共用數據時,為保證數據的一致安全,有時需要使用鎖來鎖定對象來達到同步

.NET中提供很多線程同步技術。有lock,Interlocked,Monitor等用於進程內同步鎖,Mutex互斥鎖,Semaphore信號量,Events,ReaderWriterLockSlim讀寫鎖等用於多個進程間的線程同步

1、lock

lock語句是設置對鎖定和解除鎖定的一種簡單方式,也是最常用的一種同步方式。lock用於鎖定一個引用類型欄位,當線程執行到Lock處,會鎖定該欄位,使之只有一個線程進入lock語句塊內,才lock語句結束位置再釋放鎖定,另一個線程才可以進入。原理運用同步塊索引,感興趣可以研究下

lock (obj)
{
    //synchronized region
}

因為只有一個線程可以進去,沒有併發,所以犧牲了性能,所以要儘量縮小lock的範圍,另一個建議是首選鎖一個私有變數,也就是SyncRoot模式,聲明一個syncRoot的私有object變數來進行鎖定,而不是使用lock(this),因為外面調用者也可能鎖定你這個對象的實例,但他並不知道你內部也使用了鎖,所以容易造成死鎖

private object syscRoot = new object();
public void DoThis()
{
    lock (syscRoot)
    {
        //同一個時間只有一個線程能到達這裡
    }
}

2、Interlocked

InterLoacked用於將變數的一些簡單操作原子化,也就是線程安全同步。我們常寫的i++就不是線程安全的,從記憶體中取值然後+1然後放回記憶體中,過程中很可能被其他線程打斷,比如在你+1後放回記憶體時,另一個線程已經先放回去了,也就不同步了。InerLocked類提供了以線程安全的方式遞增、遞減、交換、讀取值的方法
比如以下代替lock的遞增方式

int num = 0;
//lock (syscRoot)
//{
//    num++;
//}
num = Interlocked.Increment(ref num);

3、Monitor

上面lock就是Monitor的語法糖,通過編譯器編譯會生成Monitor的代碼,像下麵這樣

lock (syscRoot)
{
    //synchronized region
}
//上面的lock鎖等同於下麵Monitor
Monitor.Enter(syscRoot);
try
{
    //synchronized region
}
finally
{
    Monitor.Exit(syscRoot);
}

Monitor不同於Lock就是它還可以設置超時時間,不會無限制的等待下去。

bool lockTaken = false;
Monitor.TryEnter(syscRoot,500,ref lockTaken);
if (lockTaken)
{
    try
    {
        //synchronized region
    }
    finally
    {
        Monitor.Exit(syscRoot);
    }
}
else
{
}

4、SpinLock

SpinLock自旋鎖是一種用戶模式鎖。對了,插一嘴鎖分為內核模式鎖和用戶模式鎖,內核模式就是在系統級別讓線程中斷,收到信號時再切回來繼續幹活,用戶模式就是通過一些cpu指定或則死迴圈讓線程一直運行著直到可用。各有優缺點吧,內核Cpu資源利用率高,但切換損耗,用戶模式就相反,如果鎖定時間較長,就會白白迴圈等待,後面就有混合模式鎖的出現了

如果有大量的鎖定,且鎖定時間非常短,SpinLock就很有用,用法和Monitor類似,Enter或TryEnter獲取鎖,Exit釋放鎖。IsHeld和IsHeldByCurrentThread指定它當前是否鎖定

另外SpinLock是個結構類型,所以註意拷貝賦值時會創建全新副本問題。必要時可按引用來傳遞

5、Mutex

Mutex互斥鎖提供跨多個進程同步一個類,定義互斥鎖的時候可以指定互斥鎖的名稱,這樣系統能夠識別,所以在另一個進程中定義的互斥,其他進程也是可以訪問到的,Mutex.OpenExisting()便可以得到。

bool createdNew = false;
Mutex mutex = new Mutex(false"ProCharpMutex"out createdNew);
if (mutex.WaitOne())
{
    try
    {
        //synchronized region
    }
    finally
    {
        mutex.ReleaseMutex();
    }
}

介於此我們可以用來禁止一個應用程式啟動兩次,一般我們通過進程的名稱來判斷,這裡我們使用Mutex實現

bool createdNew = false;
Mutex mutex = new Mutex(false"SingletonWinAppMutex"out createdNew);
if (!createdNew)
{
    MessageBox.Show("應用程式已經啟動過了");
    Application.Exit();
    return;
}

6、Semaphore

Semaphore信號量和互斥類似,區別是,信號量可以同時讓多個線程使用,是一種計數的互斥鎖定。通過計數允許同時有幾個線程訪問受保護的資源。也可以指定信號量名稱以使在多個進程間共用

Semaphore和上面Mutex都是繼承自WaitHandle基類,WaitHandle用於等待一個信號的設置,嗲用Wait,線程會等待接收一個與等待句柄相關的信號

SemaphoreSlim是對Semaphore的輕量替代版本(它不繼承WaitHandle),SemaphoreSlim(int initialCount, int maxCount)構造函數可指定最大併發個數,然後線上程內通過SemaphoreSlim的Wait等到直到來接收信號是否可以進去受保護代碼塊了,最後記得要Release,不然下一個線程獲取不到准許進入的信號

7、Events

Events事件鎖不同於委托中的事件,在System.Threading命名空間下,用於系統範圍內的事件資源的同步,有AutoResetEvent自動事件鎖、ManualResetEvent手動事件鎖以及輕量版本ManualResetEventSlim

1、AutoResetEvent

AutoResetEvent也是繼承自waitHandle類的,也是通過WaitOne來等待直到有信號,它有兩種狀態:終止和非終止,可以調用set和reset方法使對象進入終止和非終止狀態。通俗點就是set有信號,另一個線程可以進入了,reset非終止無信息,其他線程就阻塞了。自動的意思就是一個線程進入了,自動Reset設置無信號了其他線程就進不去了。類似現實中的汽車收費口,一桿一車模式

private AutoResetEvent autoEvent = new AutoResetEvent(false);
public void DoThis()
{
    autoEvent.WaitOne();
    //執行同步代碼塊
    autoEvent.Set();
}
2、ManualResetEvent

手動事件鎖和自動的區別在於,手動事件鎖沒有信號時會阻塞一批線程的,有信號時,所有線程都運行,同時喚醒多個線程,除非手動Reset再阻塞,類似現實場景中火車道路口的柵欄,落桿攔截一批人,起桿則一批人蜂擁通過,用法和上面一樣,WaitOne等待信號,結束時通過Set來通知有信號了,可以通過了

3、ManualResetEventSlim

ManualResetEventSlim通過封裝 ManualResetEvent提供了自旋等待和內核等待的混合鎖模式。如果需要跨進程或者跨AppDomain的同步,那麼就必須使用ManualResetEvent。ManualResetEventSlim使用Wait來阻塞線程,支持任務的取消。和SemaphoreSlim的Wait一樣,內部先通過用戶模式自旋然後再通過內核模式效率更高

8、ReaderWriterLock

ReaderWriterLock讀寫鎖不是從限定線程個數的角度來保護資源,而是按讀寫角度來區分,就是你可以鎖定當某一類線程(寫線程)中一個進入受保護資源時,另一類線程(讀線程)全部阻塞。如果沒有寫入線程鎖定資源,就允許多個讀取線程方法資源,但只能有一個寫入線程鎖定該資源

具體用法參考示例

// 創建讀寫鎖
ReaderWriterLock rwLock = new ReaderWriterLock();
// 當前線程獲取讀鎖,參數為:超時值(毫秒)
rwLock.AcquireReaderLock(250);
// 判斷當前線程是否持有讀鎖
if (!rwLock.IsReaderLockHeld)
{
    return;
}
Console.WriteLine("拿到了讀鎖......");
// 將讀鎖升級為寫鎖,鎖參數為:超時值(毫秒)
LockCookie cookie = rwLock.UpgradeToWriterLock(250);
// 判斷當前線程是否持有寫鎖
if (rwLock.IsWriterLockHeld)
{
    Console.WriteLine("升級到了寫鎖......");
    // 將鎖還原到之前所的級別,也就是讀鎖
    rwLock.DowngradeFromWriterLock(ref cookie);
}
// 釋放讀鎖(減少鎖計數,直到計數達到零時,鎖被釋放)
rwLock.ReleaseReaderLock();
Console.WriteLine("順利執行完畢......");

// 當前線程獲取寫鎖,參數為:超時值(毫秒)
rwLock.AcquireWriterLock(250);
// 判斷當前線程是否持有寫鎖
if (rwLock.IsWriterLockHeld)
{
    Console.WriteLine("拿到了寫鎖......");
    // 釋放寫鎖(將減少寫鎖計數,直到計數變為零,釋放鎖)
    rwLock.ReleaseWriterLock();
}
// 釋放寫鎖(將減少寫鎖計數,直到計數變為零,釋放鎖)
// 當前線程不持有鎖,會拋出異常
rwLock.ReleaseWriterLock();
Console.WriteLine("順利執行完畢......");
Console.ReadLine();

ReaderWriterLockSlim同樣是ReaderWriterLock的輕量優化版本,簡化了遞歸、升級和降級鎖定狀態的規則。
1. EnterWriteLock 進入寫模式鎖定狀態
2. EnterReadLock 進入讀模式鎖定狀態
3. EnterUpgradeableReadLock 進入可升級的讀模式鎖定狀態
並且三種鎖定模式都有超時機制、對應 Try… 方法,退出相應的模式則使用 Exit… 方法,而且所有的方法都必須是成對出現的

二、線程安全集合

並行環境下修改共用變數為了保證資源安全,通常使用上面介紹的鎖或信號量來解決此問題。其實.NET也內置了一些線程安全的集合,使用他們就像使用單線程集合一樣。

類型描述
BlockingCollection 提供針對實現 IProducerConsumerCollection 的任何類型的限制和阻塞功能。 有關詳細信息,請參閱BlockingCollection 概述。
ConcurrentDictionary<tkey,tvalue style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;"> 鍵/值對字典的線程安全實現。
ConcurrentQueue FIFO(先進先出)隊列的線程安全實現。
ConcurrentStack LIFO(後進先出)堆棧的線程安全實現。
ConcurrentBag 無序的元素集合的線程安全實現。
IProducerConsumerCollection 類型必須實現以在 BlockingCollection 中使用的介面。

三、多線程模型

1、同步編程模型SPM

2、非同步編程模型APM

我們常見的XXBegin, XXEnd這兩個經典的配對方法就是非同步的,Begin後會委托給線程池調用一個線程去執行。還有委托的BeginInvoke調用

FileStream fs = new FileStream("D:\\test.txt", FileMode.Open);
var bytes = new byte[fs.Length];
fs.BeginRead(bytes, 0, bytes.Length, (aysc) =>
{
    var num = fs.EndRead(aysc);
}, string.Empty);

3、基於事件編程模型EAP

WinFrom/WPF開發中的BackgroundWorker類就是非同步事件模式的一種實現方案,RunWorkerAsync方法啟動與DoWork事件非同步關聯的方法,工作完成後,就觸發RunWorkerCompleted事件,也支持CancelAysnc方法取消以及ReportProgress通知進度等。還又一個典型的就

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

-Advertisement-
Play Games
更多相關文章
  • 本筆記摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/23/Event_Constructor.html,記錄一下學習過程以備後續查用。 前面講的線程同步主要是用戶模式的(CLR Via C# 一書中是這麼定義的,書中說到線程同步分兩種:一、用戶模 ...
  • 本文介紹了C#中的屬性,以及C#6和C#7中與屬性相關的新特性。 ...
  • 《.Net 最佳實踐》 [作者] (美) Stephen Ritchie[譯者] (中) 黃燈橋 黃浩宇 李永[出版] 機械工業出版社[版次] 2014年01月 第1版[印次] 2018年01月 第1次 印刷[定價] 69.00元 (P001) 開發人員應該對任何稱之為“最佳實踐”的實踐保持一種懷疑 ...
  • 目前遇到的問題: 1.路徑區分大小寫及路徑用“/”,而不是常用的"\\"。 windows下路徑為:"xxxx\\yyyy",Linux路徑下為:"xxxx/yyyy" 使用 Path.Combine("xxxx","yyyy") 進行合併即可。 2.有時候就需要在 docker 容器里訪問宿主機提 ...
  • 安裝 參考文檔:https://docs.docker.com/install/linux/docker-ce/centos/#install-using-the-repository 前提條件 Docker 要求 CentOS 系統的內核版本高於 3.10,在終端輸入以下命令: uname -r ...
  • 相關模塊 1. AbpAspNetCoreModule 2. AbpAspNetCoreMvcModule 3. AbpAspNetCoreMvcContractsModule abp通過這三個模塊載入並配置了 asp.net core。,最主要的就是AbpAspNetCoreMvcModule模塊 ...
  • 原文:https://blogs.msdn.microsoft.com/mazhou/2017/06/06/c-7-series-part-3-default-literals/ C#的default關鍵字有兩種用法:一種是標記switch…case結構的預設分支(會匹配任意不被所有case條件匹配 ...
  • 安裝Docker CentOS 7 安裝 Docker 編寫Dockerfile 右鍵項目-》添加-》Docker 支持 選擇Linux 修改為如下: FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base WORKDIR ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...