C#使用讀寫(xiě)鎖解決多線程并發(fā)問(wèn)題
一、簡(jiǎn)介
在開(kāi)發(fā)程序的過(guò)程中,難免少不了寫(xiě)入錯(cuò)誤日志這個(gè)關(guān)鍵功能。實(shí)現(xiàn)這個(gè)功能,可以選擇使用第三方日志插件,也可以選擇使用數(shù)據(jù)庫(kù),還可以自己寫(xiě)個(gè)簡(jiǎn)單的方法把錯(cuò)誤信息記錄到日志文件?,F(xiàn)在我們來(lái)講下最后一種方法:
在選擇最后一種方法實(shí)現(xiàn)的時(shí)候,若對(duì)文件操作與線程同步不熟悉,問(wèn)題就有可能出現(xiàn)了,因?yàn)橥粋€(gè)文件并不允許多個(gè)線程同時(shí)寫(xiě)入,否則會(huì)提示“文件正在由另一進(jìn)程使用,因此該進(jìn)程無(wú)法訪問(wèn)此文件”。這是文件的并發(fā)寫(xiě)入問(wèn)題,就需要用到線程同步。而微軟也給線程同步提供了一些相關(guān)的類(lèi)可以達(dá)到這樣的目的,本文使用到的 System.Threading.ReaderWriterLockSlim 便是其中之一。該類(lèi)用于管理資源訪問(wèn)的鎖定狀態(tài),可實(shí)現(xiàn)多線程讀取或進(jìn)行獨(dú)占式寫(xiě)入訪問(wèn)。利用這個(gè)類(lèi),我們就可以避免在同一時(shí)間段內(nèi)多線程同時(shí)寫(xiě)入一個(gè)文件而導(dǎo)致的并發(fā)寫(xiě)入問(wèn)題。讀寫(xiě)鎖是以 ReaderWriterLockSlim 對(duì)象作為鎖管理資源的,不同的 ReaderWriterLockSlim 對(duì)象中鎖定同一個(gè)文件也會(huì)被視為不同的鎖進(jìn)行管理,這種差異可能會(huì)再次導(dǎo)致文件的并發(fā)寫(xiě)入問(wèn)題,所以 ReaderWriterLockSlim 應(yīng)盡量定義為只讀的靜態(tài)對(duì)象。
ReaderWriterLockSlim 有幾個(gè)關(guān)鍵的方法,本文僅討論寫(xiě)入鎖:
1.調(diào)用 EnterWriteLock 方法 進(jìn)入寫(xiě)入狀態(tài),在調(diào)用線程進(jìn)入鎖定狀態(tài)之前一直處于阻塞狀態(tài),因此可能永遠(yuǎn)都不返回。
2.調(diào)用 TryEnterWriteLock 方法 進(jìn)入寫(xiě)入狀態(tài),可指定阻塞的間隔時(shí)間,如果調(diào)用線程在此間隔期間并未進(jìn)入寫(xiě)入模式,將返回false。
3.調(diào)用 ExitWriteLock 方法 退出寫(xiě)入狀態(tài),應(yīng)使用 finally 塊執(zhí)行 ExitWriteLock 方法,從而確保調(diào)用方退出寫(xiě)入模式。
二、不使用讀寫(xiě)鎖寫(xiě)入文件:
代碼:
class Program { static int LogCount = 100; static int WritedCount = 0; static int FailedCount = 0; static void Main(string[] args) { //迭代運(yùn)行寫(xiě)入日志記錄,由于多個(gè)線程同時(shí)寫(xiě)入同一個(gè)文件將會(huì)導(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 未加入讀寫(xiě)鎖 //不使用讀寫(xiě)鎖寫(xiě)入文件 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都能寫(xiě)入到log.txt,因?yàn)椴贿m用讀寫(xiě)錯(cuò)可能會(huì)出現(xiàn)上面提到的:“文件正在由另一進(jìn)程使用,因此該進(jìn)程無(wú)法訪問(wè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
三、使用讀寫(xiě)鎖寫(xiě)入文件:
代碼:
class Program { static int LogCount = 100; static int WritedCount = 0; static int FailedCount = 0; static void Main(string[] args) { //迭代運(yùn)行寫(xiě)入日志記錄,由于多個(gè)線程同時(shí)寫(xiě)入同一個(gè)文件將會(huì)導(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 加入讀寫(xiě)鎖 //讀寫(xiě)鎖,當(dāng)資源處于寫(xiě)入模式時(shí),其他線程寫(xiě)入需要等待本次寫(xiě)入結(jié)束之后才能繼續(xù)寫(xiě)入 static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim(); static void WriteLog2() { try { //設(shè)置讀寫(xiě)鎖為寫(xiě)入模式獨(dú)占資源,其他寫(xiě)入請(qǐng)求需要等待本次寫(xiě)入結(jié)束之后才能繼續(xù)寫(xiě)入 //注意:長(zhǎng)時(shí)間持有讀線程鎖或?qū)懢€程鎖會(huì)使其他線程發(fā)生饑餓 (starve)。 為了得到最好的性能,需要考慮重新構(gòu)造應(yīng)用程序以將寫(xiě)訪問(wèn)的持續(xù)時(shí)間減少到最小。 //從性能方面考慮,請(qǐng)求進(jìn)入寫(xiě)入模式應(yīng)該緊跟文件操作之前,在此處進(jìn)入寫(xiě)入模式僅是為了降低代碼復(fù)雜度 //因進(jìn)入與退出寫(xiě)入模式應(yīng)在同一個(gè)try finally語(yǔ)句塊內(nèi),所以在請(qǐng)求進(jìn)入寫(xiě)入模式之前不能觸發(fā)異常,否則釋放次數(shù)大于請(qǐng)求次數(shù)將會(huì)觸發(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 { //退出寫(xiě)入模式,釋放資源占用 //注意:一次請(qǐng)求對(duì)應(yīng)一次釋放 //若釋放次數(shù)大于請(qǐng)求次數(shù)將會(huì)觸發(fā)異常[寫(xiě)入鎖定未經(jīng)保持即被釋放] //若請(qǐng)求處理完成后未釋放將會(huì)觸發(fā)異常[此模式不下允許以遞歸方式獲取寫(xiě)入鎖定] LogWriteLock.ExitWriteLock(); } } #endregion }
運(yùn)行結(jié)果:
所有的log都完全正確寫(xiě)入到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#使用讀寫(xiě)鎖解決多線程并發(fā)問(wèn)題的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#實(shí)現(xiàn)對(duì)象序列化的3種方案小結(jié)
在上位機(jī)開(kāi)發(fā)過(guò)程中,我們可能經(jīng)常要實(shí)現(xiàn)一個(gè)數(shù)據(jù)對(duì)象的持久化,本文主要介紹了C#實(shí)現(xiàn)對(duì)象序列化的3種方案,具有一定的參考價(jià)值,感興趣的可以了解一下2025-01-01C#實(shí)現(xiàn)簡(jiǎn)易計(jì)算器小功能
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)簡(jiǎn)易計(jì)算器小功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01C#生成帶二維碼的專(zhuān)屬微信公眾號(hào)推廣海報(bào)實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于利用C#如何生成帶二維碼的專(zhuān)屬微信公眾號(hào)推廣海報(bào)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們一起來(lái)看看吧2018-12-12C# GetField方法的應(yīng)用實(shí)例講解
C#中的GetField是一個(gè)反射方法,用于獲取指定類(lèi)型的字段信息,它可以通過(guò)字段名稱(chēng)來(lái)獲取字段對(duì)象,并且可以在運(yùn)行時(shí)動(dòng)態(tài)地訪問(wèn)和操作這些字段,本文給大家介紹了C# GetField方法的應(yīng)用,需要的朋友可以參考下2024-04-04c#實(shí)現(xiàn)用SQL池,多線程定時(shí)批量執(zhí)行SQL語(yǔ)句的方法
構(gòu)建SQL池,分離業(yè)務(wù)邏輯層和數(shù)據(jù)訪問(wèn)層,讓業(yè)務(wù)邏輯層從低效的數(shù)據(jù)庫(kù)操作解脫,以提高系統(tǒng)整體性能2013-10-10