C#使用讀寫鎖解決多線程并發(fā)問題
一、簡介
在開發(fā)程序的過程中,難免少不了寫入錯(cuò)誤日志這個(gè)關(guān)鍵功能。實(shí)現(xiàn)這個(gè)功能,可以選擇使用第三方日志插件,也可以選擇使用數(shù)據(jù)庫,還可以自己寫個(gè)簡單的方法把錯(cuò)誤信息記錄到日志文件?,F(xiàn)在我們來講下最后一種方法:
在選擇最后一種方法實(shí)現(xiàn)的時(shí)候,若對文件操作與線程同步不熟悉,問題就有可能出現(xiàn)了,因?yàn)橥粋€(gè)文件并不允許多個(gè)線程同時(shí)寫入,否則會提示“文件正在由另一進(jìn)程使用,因此該進(jìn)程無法訪問此文件”。這是文件的并發(fā)寫入問題,就需要用到線程同步。而微軟也給線程同步提供了一些相關(guān)的類可以達(dá)到這樣的目的,本文使用到的 System.Threading.ReaderWriterLockSlim 便是其中之一。該類用于管理資源訪問的鎖定狀態(tài),可實(shí)現(xiàn)多線程讀取或進(jìn)行獨(dú)占式寫入訪問。利用這個(gè)類,我們就可以避免在同一時(shí)間段內(nèi)多線程同時(shí)寫入一個(gè)文件而導(dǎo)致的并發(fā)寫入問題。讀寫鎖是以 ReaderWriterLockSlim 對象作為鎖管理資源的,不同的 ReaderWriterLockSlim 對象中鎖定同一個(gè)文件也會被視為不同的鎖進(jìn)行管理,這種差異可能會再次導(dǎo)致文件的并發(fā)寫入問題,所以 ReaderWriterLockSlim 應(yīng)盡量定義為只讀的靜態(tài)對象。
ReaderWriterLockSlim 有幾個(gè)關(guān)鍵的方法,本文僅討論寫入鎖:
1.調(diào)用 EnterWriteLock 方法 進(jìn)入寫入狀態(tài),在調(diào)用線程進(jìn)入鎖定狀態(tài)之前一直處于阻塞狀態(tài),因此可能永遠(yuǎn)都不返回。
2.調(diào)用 TryEnterWriteLock 方法 進(jìn)入寫入狀態(tài),可指定阻塞的間隔時(shí)間,如果調(diào)用線程在此間隔期間并未進(jìn)入寫入模式,將返回false。
3.調(diào)用 ExitWriteLock 方法 退出寫入狀態(tài),應(yīng)使用 finally 塊執(zhí)行 ExitWriteLock 方法,從而確保調(diào)用方退出寫入模式。
二、不使用讀寫鎖寫入文件:
代碼:
class Program
{
static int LogCount = 100;
static int WritedCount = 0;
static int FailedCount = 0;
static void Main(string[] args)
{
//迭代運(yùn)行寫入日志記錄,由于多個(gè)線程同時(shí)寫入同一個(gè)文件將會導(dǎo)致錯(cuò)誤
Parallel.For(0, LogCount, e =>
{
WriteLog1();
});
Console.WriteLine(string.Format("\r\nLog Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));
Console.Read();
}
#region 未加入讀寫鎖
//不使用讀寫鎖寫入文件
static void WriteLog1()
{
try
{
var logFilePath = "log.txt";
var now = DateTime.Now;
var logContent = string.Format("Tid: {0}{1} {2}.{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), now.Millisecond.ToString());
File.AppendAllText(logFilePath, logContent);
WritedCount++;
}
catch (Exception ex)
{
FailedCount++;
Console.WriteLine(ex.Message);
}
}
#endregion
}運(yùn)行結(jié)果:
不是所有的log都能寫入到log.txt,因?yàn)椴贿m用讀寫錯(cuò)可能會出現(xiàn)上面提到的:“文件正在由另一進(jìn)程使用,因此該進(jìn)程無法訪問此文件”報(bào)錯(cuò)信息。

記錄log信息:
Tid: 9 2021年5月21日 下午 02:18:04.919 Tid: 9 2021年5月21日 下午 02:18:04.944 Tid: 9 2021年5月21日 下午 02:18:05.80 Tid: 11 2021年5月21日 下午 02:18:05.81 Tid: 9 2021年5月21日 下午 02:18:05.82 Tid: 12 2021年5月21日 下午 02:18:05.83 Tid: 11 2021年5月21日 下午 02:18:05.84 Tid: 12 2021年5月21日 下午 02:18:05.84 Tid: 16 2021年5月21日 下午 02:18:05.85 Tid: 12 2021年5月21日 下午 02:18:05.111 Tid: 16 2021年5月21日 下午 02:18:05.117 Tid: 16 2021年5月21日 下午 02:18:05.128 Tid: 11 2021年5月21日 下午 02:18:05.128 Tid: 16 2021年5月21日 下午 02:18:05.133 Tid: 12 2021年5月21日 下午 02:18:05.138 Tid: 16 2021年5月21日 下午 02:18:05.140 Tid: 12 2021年5月21日 下午 02:18:05.140 Tid: 16 2021年5月21日 下午 02:18:05.142 Tid: 16 2021年5月21日 下午 02:18:05.144 Tid: 16 2021年5月21日 下午 02:18:05.151 Tid: 16 2021年5月21日 下午 02:18:05.158 Tid: 9 2021年5月21日 下午 02:18:05.159 Tid: 10 2021年5月21日 下午 02:18:05.159 Tid: 9 2021年5月21日 下午 02:18:05.164 Tid: 16 2021年5月21日 下午 02:18:05.164 Tid: 9 2021年5月21日 下午 02:18:05.172 Tid: 15 2021年5月21日 下午 02:18:05.172 Tid: 16 2021年5月21日 下午 02:18:05.181 Tid: 16 2021年5月21日 下午 02:18:05.187 Tid: 15 2021年5月21日 下午 02:18:05.188 Tid: 16 2021年5月21日 下午 02:18:05.195 Tid: 16 2021年5月21日 下午 02:18:05.196 Tid: 15 2021年5月21日 下午 02:18:05.195 Tid: 16 2021年5月21日 下午 02:18:05.202 Tid: 16 2021年5月21日 下午 02:18:05.203 Tid: 15 2021年5月21日 下午 02:18:05.202 Tid: 15 2021年5月21日 下午 02:18:05.207 Tid: 15 2021年5月21日 下午 02:18:05.209 Tid: 16 2021年5月21日 下午 02:18:05.207 Tid: 15 2021年5月21日 下午 02:18:05.210 Tid: 15 2021年5月21日 下午 02:18:05.222 Tid: 15 2021年5月21日 下午 02:18:05.231 Tid: 18 2021年5月21日 下午 02:18:05.238 Tid: 15 2021年5月21日 下午 02:18:05.238 Tid: 18 2021年5月21日 下午 02:18:05.244 Tid: 15 2021年5月21日 下午 02:18:05.251 Tid: 15 2021年5月21日 下午 02:18:05.256 Tid: 15 2021年5月21日 下午 02:18:05.262 Tid: 15 2021年5月21日 下午 02:18:05.304 Tid: 15 2021年5月21日 下午 02:18:05.312 Tid: 13 2021年5月21日 下午 02:18:05.312 Tid: 9 2021年5月21日 下午 02:18:05.313 Tid: 13 2021年5月21日 下午 02:18:05.320 Tid: 19 2021年5月21日 下午 02:18:05.320 Tid: 16 2021年5月21日 下午 02:18:05.325 Tid: 19 2021年5月21日 下午 02:18:05.333 Tid: 16 2021年5月21日 下午 02:18:05.342 Tid: 16 2021年5月21日 下午 02:18:05.349 Tid: 16 2021年5月21日 下午 02:18:05.361 Tid: 16 2021年5月21日 下午 02:18:05.366 Tid: 16 2021年5月21日 下午 02:18:05.367 Tid: 16 2021年5月21日 下午 02:18:05.368 Tid: 16 2021年5月21日 下午 02:18:05.376 Tid: 16 2021年5月21日 下午 02:18:05.386 Tid: 16 2021年5月21日 下午 02:18:05.392 Tid: 16 2021年5月21日 下午 02:18:05.401 Tid: 9 2021年5月21日 下午 02:18:05.463 Tid: 13 2021年5月21日 下午 02:18:05.464 Tid: 15 2021年5月21日 下午 02:18:05.464 Tid: 13 2021年5月21日 下午 02:18:05.465 Tid: 13 2021年5月21日 下午 02:18:05.470 Tid: 11 2021年5月21日 下午 02:18:05.479
三、使用讀寫鎖寫入文件:
代碼:
class Program
{
static int LogCount = 100;
static int WritedCount = 0;
static int FailedCount = 0;
static void Main(string[] args)
{
//迭代運(yùn)行寫入日志記錄,由于多個(gè)線程同時(shí)寫入同一個(gè)文件將會導(dǎo)致錯(cuò)誤
Parallel.For(0, LogCount, e =>
{
WriteLog2();
});
Console.WriteLine(string.Format("\r\nLog Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));
Console.Read();
}
#region 加入讀寫鎖
//讀寫鎖,當(dāng)資源處于寫入模式時(shí),其他線程寫入需要等待本次寫入結(jié)束之后才能繼續(xù)寫入
static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
static void WriteLog2()
{
try
{
//設(shè)置讀寫鎖為寫入模式獨(dú)占資源,其他寫入請求需要等待本次寫入結(jié)束之后才能繼續(xù)寫入
//注意:長時(shí)間持有讀線程鎖或?qū)懢€程鎖會使其他線程發(fā)生饑餓 (starve)。 為了得到最好的性能,需要考慮重新構(gòu)造應(yīng)用程序以將寫訪問的持續(xù)時(shí)間減少到最小。
//從性能方面考慮,請求進(jìn)入寫入模式應(yīng)該緊跟文件操作之前,在此處進(jìn)入寫入模式僅是為了降低代碼復(fù)雜度
//因進(jìn)入與退出寫入模式應(yīng)在同一個(gè)try finally語句塊內(nèi),所以在請求進(jìn)入寫入模式之前不能觸發(fā)異常,否則釋放次數(shù)大于請求次數(shù)將會觸發(fā)異常
LogWriteLock.EnterWriteLock();
var logFilePath = "log.txt";
var now = DateTime.Now;
var logContent = string.Format("Tid: {0}{1} {2}.{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), now.Millisecond.ToString());
File.AppendAllText(logFilePath, logContent);
WritedCount++;
}
catch (Exception)
{
FailedCount++;
}
finally
{
//退出寫入模式,釋放資源占用
//注意:一次請求對應(yīng)一次釋放
//若釋放次數(shù)大于請求次數(shù)將會觸發(fā)異常[寫入鎖定未經(jīng)保持即被釋放]
//若請求處理完成后未釋放將會觸發(fā)異常[此模式不下允許以遞歸方式獲取寫入鎖定]
LogWriteLock.ExitWriteLock();
}
}
#endregion
}運(yùn)行結(jié)果:
所有的log都完全正確寫入到log.txt。

記錄log信息:
Tid: 8 2021年5月21日 下午 02:26:36.573 Tid: 8 2021年5月21日 下午 02:26:36.597 Tid: 8 2021年5月21日 下午 02:26:36.599 Tid: 8 2021年5月21日 下午 02:26:36.600 Tid: 8 2021年5月21日 下午 02:26:36.601 Tid: 8 2021年5月21日 下午 02:26:36.602 Tid: 8 2021年5月21日 下午 02:26:36.608 Tid: 8 2021年5月21日 下午 02:26:36.609 Tid: 8 2021年5月21日 下午 02:26:36.614 Tid: 8 2021年5月21日 下午 02:26:36.616 Tid: 8 2021年5月21日 下午 02:26:36.617 Tid: 8 2021年5月21日 下午 02:26:36.620 Tid: 8 2021年5月21日 下午 02:26:36.620 Tid: 8 2021年5月21日 下午 02:26:36.621 Tid: 8 2021年5月21日 下午 02:26:36.622 Tid: 8 2021年5月21日 下午 02:26:36.623 Tid: 8 2021年5月21日 下午 02:26:36.624 Tid: 8 2021年5月21日 下午 02:26:36.624 Tid: 8 2021年5月21日 下午 02:26:36.625 Tid: 8 2021年5月21日 下午 02:26:36.626 Tid: 8 2021年5月21日 下午 02:26:36.626 Tid: 8 2021年5月21日 下午 02:26:36.627 Tid: 8 2021年5月21日 下午 02:26:36.628 Tid: 8 2021年5月21日 下午 02:26:36.628 Tid: 8 2021年5月21日 下午 02:26:36.629 Tid: 8 2021年5月21日 下午 02:26:36.630 Tid: 8 2021年5月21日 下午 02:26:36.630 Tid: 8 2021年5月21日 下午 02:26:36.631 Tid: 8 2021年5月21日 下午 02:26:36.632 Tid: 8 2021年5月21日 下午 02:26:36.632 Tid: 8 2021年5月21日 下午 02:26:36.633 Tid: 8 2021年5月21日 下午 02:26:36.634 Tid: 8 2021年5月21日 下午 02:26:36.634 Tid: 8 2021年5月21日 下午 02:26:36.635 Tid: 8 2021年5月21日 下午 02:26:36.636 Tid: 8 2021年5月21日 下午 02:26:36.636 Tid: 8 2021年5月21日 下午 02:26:36.637 Tid: 8 2021年5月21日 下午 02:26:36.638 Tid: 8 2021年5月21日 下午 02:26:36.638 Tid: 8 2021年5月21日 下午 02:26:36.639 Tid: 8 2021年5月21日 下午 02:26:36.641 Tid: 8 2021年5月21日 下午 02:26:36.641 Tid: 8 2021年5月21日 下午 02:26:36.642 Tid: 8 2021年5月21日 下午 02:26:36.643 Tid: 8 2021年5月21日 下午 02:26:36.644 Tid: 8 2021年5月21日 下午 02:26:36.644 Tid: 8 2021年5月21日 下午 02:26:36.645 Tid: 8 2021年5月21日 下午 02:26:36.646 Tid: 8 2021年5月21日 下午 02:26:36.647 Tid: 8 2021年5月21日 下午 02:26:36.647 Tid: 8 2021年5月21日 下午 02:26:36.648 Tid: 8 2021年5月21日 下午 02:26:36.649 Tid: 8 2021年5月21日 下午 02:26:36.650 Tid: 8 2021年5月21日 下午 02:26:36.650 Tid: 8 2021年5月21日 下午 02:26:36.651 Tid: 8 2021年5月21日 下午 02:26:36.652 Tid: 8 2021年5月21日 下午 02:26:36.652 Tid: 8 2021年5月21日 下午 02:26:36.652 Tid: 8 2021年5月21日 下午 02:26:36.653 Tid: 8 2021年5月21日 下午 02:26:36.654 Tid: 8 2021年5月21日 下午 02:26:36.655 Tid: 8 2021年5月21日 下午 02:26:36.656 Tid: 8 2021年5月21日 下午 02:26:36.658 Tid: 8 2021年5月21日 下午 02:26:36.658 Tid: 8 2021年5月21日 下午 02:26:36.659 Tid: 8 2021年5月21日 下午 02:26:36.660 Tid: 8 2021年5月21日 下午 02:26:36.660 Tid: 8 2021年5月21日 下午 02:26:36.661 Tid: 8 2021年5月21日 下午 02:26:36.662 Tid: 8 2021年5月21日 下午 02:26:36.662 Tid: 8 2021年5月21日 下午 02:26:36.663 Tid: 8 2021年5月21日 下午 02:26:36.664 Tid: 8 2021年5月21日 下午 02:26:36.664 Tid: 8 2021年5月21日 下午 02:26:36.665 Tid: 8 2021年5月21日 下午 02:26:36.666 Tid: 8 2021年5月21日 下午 02:26:36.666 Tid: 8 2021年5月21日 下午 02:26:36.667 Tid: 8 2021年5月21日 下午 02:26:36.668 Tid: 8 2021年5月21日 下午 02:26:36.669 Tid: 8 2021年5月21日 下午 02:26:36.669 Tid: 8 2021年5月21日 下午 02:26:36.670 Tid: 8 2021年5月21日 下午 02:26:36.671 Tid: 8 2021年5月21日 下午 02:26:36.672 Tid: 8 2021年5月21日 下午 02:26:36.673 Tid: 8 2021年5月21日 下午 02:26:36.675 Tid: 8 2021年5月21日 下午 02:26:36.675 Tid: 8 2021年5月21日 下午 02:26:36.676 Tid: 8 2021年5月21日 下午 02:26:36.677 Tid: 14 2021年5月21日 下午 02:26:36.678 Tid: 15 2021年5月21日 下午 02:26:36.679 Tid: 16 2021年5月21日 下午 02:26:36.680 Tid: 17 2021年5月21日 下午 02:26:36.681 Tid: 18 2021年5月21日 下午 02:26:36.681 Tid: 20 2021年5月21日 下午 02:26:36.683 Tid: 9 2021年5月21日 下午 02:26:36.683 Tid: 19 2021年5月21日 下午 02:26:36.684 Tid: 10 2021年5月21日 下午 02:26:36.685 Tid: 11 2021年5月21日 下午 02:26:36.685 Tid: 12 2021年5月21日 下午 02:26:36.687 Tid: 13 2021年5月21日 下午 02:26:36.688
到此這篇關(guān)于C#使用讀寫鎖解決多線程并發(fā)問題的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#獲取進(jìn)程的主窗口句柄的實(shí)現(xiàn)方法
C#獲取進(jìn)程的主窗口句柄的實(shí)現(xiàn)方法,需要的朋友可以參考一下2013-04-04
C#使用CryptoStream類加密和解密字符串的實(shí)現(xiàn)
CryptoStream設(shè)計(jì)用于在內(nèi)容以流的形式輸出到文件時(shí)加密和解密內(nèi)容,本文主要介紹了C#使用CryptoStream類加密和解密字符串的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
在winform下實(shí)現(xiàn)左右布局多窗口界面的方法之續(xù)篇
這篇文章主要介紹了在winform下實(shí)現(xiàn)左右布局多窗口界面的方法之續(xù)篇 的相關(guān)資料,需要的朋友可以參考下2016-02-02
Unity3D基于OnGUI實(shí)時(shí)顯示FPS
這篇文章主要介紹了Unity3D基于OnGUI實(shí)時(shí)顯示FPS,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
C#定時(shí)器實(shí)現(xiàn)自動執(zhí)行的方法
這篇文章主要介紹了C#定時(shí)器實(shí)現(xiàn)自動執(zhí)行的方法,實(shí)例分析了C#定時(shí)器參數(shù)的設(shè)置及方法的調(diào)用與實(shí)現(xiàn),需要的朋友可以參考下2015-01-01

