C#多線程系列之讀寫鎖
本篇的內(nèi)容主要是介紹 ReaderWriterLockSlim 類,來實(shí)現(xiàn)多線程下的讀寫分離。
ReaderWriterLockSlim
ReaderWriterLock 類:定義支持單個(gè)寫線程和多個(gè)讀線程的鎖。
ReaderWriterLockSlim 類:表示用于管理資源訪問的鎖定狀態(tài),可實(shí)現(xiàn)多線程讀取或進(jìn)行獨(dú)占式寫入訪問。
兩者的 API 十分接近,而且 ReaderWriterLockSlim 相對(duì) ReaderWriterLock 來說 更加安全。因此本文主要講解 ReaderWriterLockSlim 。
兩者都是實(shí)現(xiàn)多個(gè)線程可同時(shí)讀取、只允許一個(gè)線程寫入的類。
ReaderWriterLockSlim
老規(guī)矩,先大概了解一下 ReaderWriterLockSlim 常用的方法。
常用方法
| 方法 | 說明 |
|---|---|
| EnterReadLock() | 嘗試進(jìn)入讀取模式鎖定狀態(tài)。 |
| EnterUpgradeableReadLock() | 嘗試進(jìn)入可升級(jí)模式鎖定狀態(tài)。 |
| EnterWriteLock() | 嘗試進(jìn)入寫入模式鎖定狀態(tài)。 |
| ExitReadLock() | 減少讀取模式的遞歸計(jì)數(shù),并在生成的計(jì)數(shù)為 0(零)時(shí)退出讀取模式。 |
| ExitUpgradeableReadLock() | 減少可升級(jí)模式的遞歸計(jì)數(shù),并在生成的計(jì)數(shù)為 0(零)時(shí)退出可升級(jí)模式。 |
| ExitWriteLock() | 減少寫入模式的遞歸計(jì)數(shù),并在生成的計(jì)數(shù)為 0(零)時(shí)退出寫入模式。 |
| TryEnterReadLock(Int32) | 嘗試進(jìn)入讀取模式鎖定狀態(tài),可以選擇整數(shù)超時(shí)時(shí)間。 |
| TryEnterReadLock(TimeSpan) | 嘗試進(jìn)入讀取模式鎖定狀態(tài),可以選擇超時(shí)時(shí)間。 |
| TryEnterUpgradeableReadLock(Int32) | 嘗試進(jìn)入可升級(jí)模式鎖定狀態(tài),可以選擇超時(shí)時(shí)間。 |
| TryEnterUpgradeableReadLock(TimeSpan) | 嘗試進(jìn)入可升級(jí)模式鎖定狀態(tài),可以選擇超時(shí)時(shí)間。 |
| TryEnterWriteLock(Int32) | 嘗試進(jìn)入寫入模式鎖定狀態(tài),可以選擇超時(shí)時(shí)間。 |
| TryEnterWriteLock(TimeSpan) | 嘗試進(jìn)入寫入模式鎖定狀態(tài),可以選擇超時(shí)時(shí)間。 |
ReaderWriterLockSlim 的讀、寫入鎖模板如下:
private static ReaderWriterLockSlim toolLock = new ReaderWriterLockSlim();
// 讀
private T Read()
{
try
{
toolLock.EnterReadLock(); // 獲取讀取鎖
return obj;
}
catch { }
finally
{
toolLock.ExitReadLock(); // 釋放讀取鎖
}
return default;
}
// 寫
public void Write(int key, int value)
{
try
{
toolLock.EnterUpgradeableReadLock();
try
{
toolLock.EnterWriteLock();
/*
*
*/
}
catch
{
}
finally
{
toolLock.ExitWriteLock();
}
}
catch { }
finally
{
toolLock.ExitUpgradeableReadLock();
}
}訂單系統(tǒng)示例
這里來模擬一個(gè)簡(jiǎn)單粗糙的訂單系統(tǒng)。
開始編寫代碼前,先來了解一些方法的具體使用。
EnterReadLock() / TryEnterReadLock 和 ExitReadLock() 成對(duì)出現(xiàn)。
EnterWriteLock() / TryEnterWriteLock() 和 ExitWriteLock() 成對(duì)出現(xiàn)。
EnterUpgradeableReadLock() 進(jìn)入可升級(jí)的讀模式鎖定狀態(tài)。
EnterReadLock() 使用 EnterUpgradeableReadLock() 進(jìn)入升級(jí)狀態(tài),在恰當(dāng)時(shí)間點(diǎn) 通過 EnterWriteLock() 進(jìn)入寫模式。(也可以倒過來)
定義三個(gè)變量:
ReaderWriterLockSlim 多線程讀寫鎖;
MaxId 當(dāng)前訂單 Id 的最大值;
orders 訂單表;
private static ReaderWriterLockSlim tool = new ReaderWriterLockSlim(); // 讀寫鎖
private static int MaxId = 1;
public static List<DoWorkModel> orders = new List<DoWorkModel>(); // 訂單表 // 訂單模型
public class DoWorkModel
{
public int Id { get; set; } // 訂單號(hào)
public string UserName { get; set; } // 客戶名稱
public DateTime DateTime { get; set; } // 創(chuàng)建時(shí)間
}然后實(shí)現(xiàn)查詢和創(chuàng)建訂單的兩個(gè)方法。
分頁查詢訂單:
在讀取前使用 EnterReadLock() 獲取鎖;
讀取完畢后,使用 ExitReadLock() 釋放鎖。
這樣能夠在多線程環(huán)境下保證每次讀取都是最新的值。
// 分頁查詢訂單
private static DoWorkModel[] DoSelect(int pageNo, int pageSize)
{
try
{
DoWorkModel[] doWorks;
tool.EnterReadLock(); // 獲取讀取鎖
doWorks = orders.Skip((pageNo - 1) * pageSize).Take(pageSize).ToArray();
return doWorks;
}
catch { }
finally
{
tool.ExitReadLock(); // 釋放讀取鎖
}
return default;
}創(chuàng)建訂單:
創(chuàng)建訂單的信息十分簡(jiǎn)單,知道用戶名和創(chuàng)建時(shí)間就行。
訂單系統(tǒng)要保證的時(shí)每個(gè) Id 都是唯一的(實(shí)際情況應(yīng)該用Guid),這里為了演示讀寫鎖,設(shè)置為 數(shù)字。
在多線程環(huán)境下,我們不使用 Interlocked.Increment() ,而是直接使用 += 1,因?yàn)橛凶x寫鎖的存在,所以操作也是原則性的。
// 創(chuàng)建訂單
private static DoWorkModel DoCreate(string userName, DateTime time)
{
try
{
tool.EnterUpgradeableReadLock(); // 升級(jí)
try
{
tool.EnterWriteLock(); // 獲取寫入鎖
// 寫入訂單
MaxId += 1; // Interlocked.Increment(ref MaxId);
DoWorkModel model = new DoWorkModel
{
Id = MaxId,
UserName = userName,
DateTime = time
};
orders.Add(model);
return model;
}
catch { }
finally
{
tool.ExitWriteLock(); // 釋放寫入鎖
}
}
catch { }
finally
{
tool.ExitUpgradeableReadLock(); // 降級(jí)
}
return default;
}Main 方法中:
開 5 個(gè)線程,不斷地讀,開 2 個(gè)線程不斷地創(chuàng)建訂單。線程創(chuàng)建訂單時(shí)是沒有設(shè)置 Thread.Sleep() 的,因此運(yùn)行速度十分快。
Main 方法里面的代碼沒有什么意義。
static void Main(string[] args)
{
// 5個(gè)線程讀
for (int i = 0; i < 5; i++)
{
new Thread(() =>
{
while (true)
{
var result = DoSelect(1, MaxId);
if (result is null)
{
Console.WriteLine("獲取失敗");
continue;
}
foreach (var item in result)
{
Console.Write($"{item.Id}|");
}
Console.WriteLine("\n");
Thread.Sleep(1000);
}
}).Start();
}
for (int i = 0; i < 2; i++)
{
new Thread(() =>
{
while(true)
{
var result = DoCreate((new Random().Next(0, 100)).ToString(), DateTime.Now); // 模擬生成訂單
if (result is null)
Console.WriteLine("創(chuàng)建失敗");
else Console.WriteLine("創(chuàng)建成功");
}
}).Start();
}
}在 ASP.NET Core 中,則可以利用讀寫鎖,解決多用戶同時(shí)發(fā)送 HTTP 請(qǐng)求帶來的數(shù)據(jù)庫(kù)讀寫問題。
這里就不做示例了。
如果另一個(gè)線程發(fā)生問題,導(dǎo)致遲遲不能交出寫入鎖,那么可能會(huì)導(dǎo)致其它線程無限等待。
那么可以使用 TryEnterWriteLock() 并且設(shè)置等待時(shí)間,避免阻塞時(shí)間過長(zhǎng)。
bool isGet = tool.TryEnterWriteLock(500);
并發(fā)字典寫示例
因?yàn)槔碚摰臇|西,筆者這里不會(huì)說太多,主要就是先掌握一些 API(方法、屬性) 的使用,然后簡(jiǎn)單寫出示例,后面再慢慢深入了解底層原理。
這里來寫一個(gè)多線程共享使用字典(Dictionary)的使用示例。
增加兩個(gè)靜態(tài)變量:
private static ReaderWriterLockSlim toolLock = new ReaderWriterLockSlim();
private static Dictionary<int, int> dict = new Dictionary<int, int>();實(shí)現(xiàn)一個(gè)寫操作:
public static void Write(int key, int value)
{
try
{
// 升級(jí)狀態(tài)
toolLock.EnterUpgradeableReadLock();
// 讀,檢查是否存在
if (dict.ContainsKey(key))
return;
try
{
// 進(jìn)入寫狀態(tài)
toolLock.EnterWriteLock();
dict.Add(key,value);
}
finally
{
toolLock.ExitWriteLock();
}
}
finally
{
toolLock.ExitUpgradeableReadLock();
}
}上面沒有 catch { } 是為了更好觀察代碼,因?yàn)槭褂昧俗x寫鎖,理論上不應(yīng)該出現(xiàn)問題的。
模擬五個(gè)線程同時(shí)寫入字典,由于不是原子操作,所以 sum 的值有些時(shí)候會(huì)出現(xiàn)重復(fù)值。
原子操作請(qǐng)參考:http://www.dbjr.com.cn/article/237310.htm
private static int sum = 0;
public static void AddOne()
{
for (int i = 0; i < 100_0000; i++)
{
sum += 1;
Write(sum,sum);
}
}
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
new Thread(() => { AddOne(); }).Start();
Console.ReadKey();
}ReaderWriterLock
大多數(shù)情況下都是推薦 ReaderWriterLockSlim 的,而且兩者的使用方法十分接近。
例如 AcquireReaderLock 是獲取讀鎖,AcquireWriterLock 獲取寫鎖。使用對(duì)應(yīng)的方法即可替換 ReaderWriterLockSlim 中的示例。
這里就不對(duì) ReaderWriterLock 進(jìn)行贅述了。
ReaderWriterLock 的常用方法如下:
| 方法 | 說明 |
|---|---|
| AcquireReaderLock(Int32) | 使用一個(gè) Int32 超時(shí)值獲取讀線程鎖。 |
| AcquireReaderLock(TimeSpan) | 使用一個(gè) TimeSpan 超時(shí)值獲取讀線程鎖。 |
| AcquireWriterLock(Int32) | 使用一個(gè) Int32 超時(shí)值獲取寫線程鎖。 |
| AcquireWriterLock(TimeSpan) | 使用一個(gè) TimeSpan 超時(shí)值獲取寫線程鎖。 |
| AnyWritersSince(Int32) | 指示獲取序列號(hào)之后是否已將寫線程鎖授予某個(gè)線程。 |
| DowngradeFromWriterLock(LockCookie) | 將線程的鎖狀態(tài)還原為調(diào)用 UpgradeToWriterLock(Int32) 前的狀態(tài)。 |
| ReleaseLock() | 釋放鎖,不管線程獲取鎖的次數(shù)如何。 |
| ReleaseReaderLock() | 減少鎖計(jì)數(shù)。 |
| ReleaseWriterLock() | 減少寫線程鎖上的鎖計(jì)數(shù)。 |
| RestoreLock(LockCookie) | 將線程的鎖狀態(tài)還原為調(diào)用 ReleaseLock() 前的狀態(tài)。 |
| UpgradeToWriterLock(Int32) | 使用一個(gè) Int32 超時(shí)值將讀線程鎖升級(jí)為寫線程鎖。 |
| UpgradeToWriterLock(TimeSpan) | 使用一個(gè) TimeSpan 超時(shí)值將讀線程鎖升級(jí)為寫線程鎖。 |
官方示例可以看:
到此這篇關(guān)于C#多線程系列之讀寫鎖的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#中事務(wù)處理和非事務(wù)處理方法實(shí)例分析
這篇文章主要介紹了C#中事務(wù)處理和非事務(wù)處理方法,較為詳細(xì)的分析了C#中事務(wù)處理與非事務(wù)處理的使用技巧,對(duì)于使用C#進(jìn)行數(shù)據(jù)庫(kù)程序開發(fā)有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
c#使用EPPlus將圖片流嵌入到Excel實(shí)現(xiàn)示例
這篇文章主要為大家介紹了c#使用EPPlus將圖片流嵌入到Excel實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
在.NET?WebService中跨域CORS問題的解決方案
在現(xiàn)代的Web應(yīng)用程序開發(fā)中,跨域資源共享(Cross-Origin?Resource?Sharing,?CORS)問題是開發(fā)者經(jīng)常遇到的一個(gè)挑戰(zhàn),在這篇博客中,我們將深入探討如何在?.NET?WebService?中解決CORS問題,幫助開發(fā)者順利實(shí)現(xiàn)跨域請(qǐng)求,需要的朋友可以參考下2024-05-05
C#中的IEnumerable簡(jiǎn)介及簡(jiǎn)單實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了C#中的IEnumerable簡(jiǎn)介及簡(jiǎn)單實(shí)現(xiàn)實(shí)例,本文講解了IEnumerable一些知識(shí)并給出了一個(gè)簡(jiǎn)單的實(shí)現(xiàn),需要的朋友可以參考下2015-03-03
C#中哈希表(Hashtable)的介紹及簡(jiǎn)單用法
在.NET Framework中,Hashtable是System.Collections命名空間提供的一個(gè)容器,用于處理和表現(xiàn)類似key/value的鍵值對(duì)2013-03-03
C#導(dǎo)出pdf的實(shí)現(xiàn)方法(瀏覽器不預(yù)覽直接下載)
這篇文章主要給大家介紹了關(guān)于C#導(dǎo)出pdf的實(shí)現(xiàn)方法,實(shí)現(xiàn)后瀏覽器不預(yù)覽就可以直接下載,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12

