C#在復(fù)雜多線程環(huán)境下使用讀寫鎖同步寫入文件
代碼一:
class Program { static int LogCount = 1000; static int SumLogCount = 0; static int WritedCount = 0; static int FailedCount = 0; static void Main(string[] args) { //往線程池里添加一個(gè)任務(wù),迭代寫入N個(gè)日志 SumLogCount += LogCount; ThreadPool.QueueUserWorkItem((obj) => { Parallel.For(0, LogCount, e => { WriteLog(); }); }); //在新的線程里,添加N個(gè)寫入日志的任務(wù)到線程池 SumLogCount += LogCount; var thread1 = new Thread(() => { Parallel.For(0, LogCount, e => { ThreadPool.QueueUserWorkItem((subObj) => { WriteLog(); }); }); }); thread1.IsBackground = false; thread1.Start(); //添加N個(gè)寫入日志的任務(wù)到線程池 SumLogCount += LogCount; Parallel.For(0, LogCount, e => { ThreadPool.QueueUserWorkItem((obj) => { WriteLog(); }); }); //在新的線程里,迭代寫入N個(gè)日志 SumLogCount += LogCount; var thread2 = new Thread(() => { Parallel.For(0, LogCount, e => { WriteLog(); }); }); thread2.IsBackground = false; thread2.Start(); //在當(dāng)前線程里,迭代寫入N個(gè)日志 SumLogCount += LogCount; Parallel.For(0, LogCount, e => { WriteLog(); }); Console.WriteLine("Main Thread Processed.\r\n"); while (true) { Console.WriteLine(string.Format("Sum Log Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", SumLogCount.ToString(), WritedCount.ToString(), FailedCount.ToString())); Console.ReadLine(); } } //讀寫鎖,當(dāng)資源處于寫入模式時(shí),其他線程寫入需要等待本次寫入結(jié)束之后才能繼續(xù)寫入 static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim(); static void WriteLog() { try { //設(shè)置讀寫鎖為寫入模式獨(dú)占資源,其他寫入請求需要等待本次寫入結(jié)束之后才能繼續(xù)寫入 //注意:長時(shí)間持有讀線程鎖或?qū)懢€程鎖會(huì)使其他線程發(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ù)將會(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 { //退出寫入模式,釋放資源占用 //注意:一次請求對應(yīng)一次釋放 // 若釋放次數(shù)大于請求次數(shù)將會(huì)觸發(fā)異常[寫入鎖定未經(jīng)保持即被釋放] // 若請求處理完成后未釋放將會(huì)觸發(fā)異常[此模式不下允許以遞歸方式獲取寫入鎖定] LogWriteLock.ExitWriteLock(); } } }
運(yùn)行結(jié)果:
復(fù)雜多線程環(huán)境下使用讀寫鎖,全部日志成功寫入了日志文件,由ThreadId和DateTime可以看出是由不同的線程同步寫入。
代碼二:
class Program { static void Main(string[] args) { #region 簡單使用 //var mutexKey = MutexExample.GetFilePathMutexKey("文件路徑"); //MutexExample.MutexExec(mutexKey, () => //{ // Console.WriteLine("需要進(jìn)程同步執(zhí)行的代碼"); //}); #endregion #region 測試代碼 //D:\Winform\多線程\多線程_互斥信號量\bin\Debug\TEST.LOG var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.log").ToUpper(); var mutexKey = MutexExample.GetFilePathMutexKey(filePath); //同時(shí)開啟N個(gè)寫入線程 Parallel.For(0, LogCount, e => { //沒使用互斥鎖操作寫入,大量寫入錯(cuò)誤;FileStream包含F(xiàn)ileShare的構(gòu)造函數(shù)也僅實(shí)現(xiàn)了進(jìn)程內(nèi)的線程同步,多進(jìn)程同時(shí)寫入時(shí)也會(huì)出錯(cuò) //WriteLog(filePath); //使用互斥鎖操作寫入,由于同一時(shí)間僅有一個(gè)線程操作,所以不會(huì)出錯(cuò) MutexExample.MutexExec(mutexKey, () => { WriteLog(filePath); }); }); Console.WriteLine(string.Format("Log Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString())); Console.Read(); #endregion } /// <summary> /// C#互斥量使用示例代碼 /// </summary> /// <remarks>已在經(jīng)過測試并上線運(yùn)行,可直接使用</remarks> public static class MutexExample { /// <summary> /// 進(jìn)程間同步執(zhí)行的簡單例子 /// </summary> /// <param name="action">同步處理代碼</param> /// <param name="mutexKey">操作系統(tǒng)級的同步鍵 /// (如果將 name 指定為 null 或空字符串,則創(chuàng)建一個(gè)局部互斥體。 /// 如果名稱以前綴“Global\”開頭,則 mutex 在所有終端服務(wù)器會(huì)話中均為可見。 /// 如果名稱以前綴“Local\”開頭,則 mutex 僅在創(chuàng)建它的終端服務(wù)器會(huì)話中可見。 /// 如果創(chuàng)建已命名 mutex 時(shí)不指定前綴,則它將采用前綴“Local\”。)</param> /// <remarks>不重試且不考慮異常情況處理的簡單例子</remarks> [Obsolete(error: false, message: "請使用MutexExec")] public static void MutexExecEasy(string mutexKey, Action action) { //聲明一個(gè)已命名的互斥體,實(shí)現(xiàn)進(jìn)程間同步;該命名互斥體不存在則自動(dòng)創(chuàng)建,已存在則直接獲取 using (Mutex mut = new Mutex(false, mutexKey)) { try { //上鎖,其他線程需等待釋放鎖之后才能執(zhí)行處理;若其他線程已經(jīng)上鎖或優(yōu)先上鎖,則先等待其他線程執(zhí)行完畢 mut.WaitOne(); //執(zhí)行處理代碼(在調(diào)用WaitHandle.WaitOne至WaitHandle.ReleaseMutex的時(shí)間段里,只有一個(gè)線程處理,其他線程都得等待釋放鎖后才能執(zhí)行該代碼段) action(); } finally { //釋放鎖,讓其他進(jìn)程(或線程)得以繼續(xù)執(zhí)行 mut.ReleaseMutex(); } } } /// <summary> /// 獲取文件名對應(yīng)的進(jìn)程同步鍵 /// </summary> /// <param name="filePath">文件路徑(請注意大小寫及空格)</param> /// <returns>進(jìn)程同步鍵(互斥體名稱)</returns> public static string GetFilePathMutexKey(string filePath) { //生成文件對應(yīng)的同步鍵,可自定義格式(互斥體名稱對特殊字符支持不友好,遂轉(zhuǎn)換為BASE64格式字符串) var fileKey = Convert.ToBase64String(Encoding.Default.GetBytes(string.Format(@"FILE\{0}", filePath))); //轉(zhuǎn)換為操作系統(tǒng)級的同步鍵 var mutexKey = string.Format(@"Global\{0}", fileKey); return mutexKey; } /// <summary> /// 進(jìn)程間同步執(zhí)行 /// </summary> /// <param name="mutexKey">操作系統(tǒng)級的同步鍵 /// (如果將 name 指定為 null 或空字符串,則創(chuàng)建一個(gè)局部互斥體。 /// 如果名稱以前綴“Global\”開頭,則 mutex 在所有終端服務(wù)器會(huì)話中均為可見。 /// 如果名稱以前綴“Local\”開頭,則 mutex 僅在創(chuàng)建它的終端服務(wù)器會(huì)話中可見。 /// 如果創(chuàng)建已命名 mutex 時(shí)不指定前綴,則它將采用前綴“Local\”。)</param> /// <param name="action">同步處理操作</param> public static void MutexExec(string mutexKey, Action action) { MutexExec(mutexKey: mutexKey, action: action, recursive: false); } /// <summary> /// 進(jìn)程間同步執(zhí)行 /// </summary> /// <param name="mutexKey">操作系統(tǒng)級的同步鍵 /// (如果將 name 指定為 null 或空字符串,則創(chuàng)建一個(gè)局部互斥體。 /// 如果名稱以前綴“Global\”開頭,則 mutex 在所有終端服務(wù)器會(huì)話中均為可見。 /// 如果名稱以前綴“Local\”開頭,則 mutex 僅在創(chuàng)建它的終端服務(wù)器會(huì)話中可見。 /// 如果創(chuàng)建已命名 mutex 時(shí)不指定前綴,則它將采用前綴“Local\”。)</param> /// <param name="action">同步處理操作</param> /// <param name="recursive">指示當(dāng)前調(diào)用是否為遞歸處理,遞歸處理時(shí)檢測到異常則拋出異常,避免進(jìn)入無限遞歸</param> private static void MutexExec(string mutexKey, Action action, bool recursive) { //聲明一個(gè)已命名的互斥體,實(shí)現(xiàn)進(jìn)程間同步;該命名互斥體不存在則自動(dòng)創(chuàng)建,已存在則直接獲取 //initiallyOwned: false:默認(rèn)當(dāng)前線程并不擁有已存在互斥體的所屬權(quán),即默認(rèn)本線程并非為首次創(chuàng)建該命名互斥體的線程 //注意:并發(fā)聲明同名的命名互斥體時(shí),若間隔時(shí)間過短,則可能同時(shí)聲明了多個(gè)名稱相同的互斥體,并且同名的多個(gè)互斥體之間并不同步,高并發(fā)用戶請另行處理 using (Mutex mut = new Mutex(initiallyOwned: false, name: mutexKey)) { try { //上鎖,其他線程需等待釋放鎖之后才能執(zhí)行處理;若其他線程已經(jīng)上鎖或優(yōu)先上鎖,則先等待其他線程執(zhí)行完畢 mut.WaitOne(); //執(zhí)行處理代碼(在調(diào)用WaitHandle.WaitOne至WaitHandle.ReleaseMutex的時(shí)間段里,只有一個(gè)線程處理,其他線程都得等待釋放鎖后才能執(zhí)行該代碼段) action(); } //當(dāng)其他進(jìn)程已上鎖且沒有正常釋放互斥鎖時(shí)(譬如進(jìn)程忽然關(guān)閉或退出),則會(huì)拋出AbandonedMutexException異常 catch (AbandonedMutexException ex) { //避免進(jìn)入無限遞歸 if (recursive) throw ex; //非遞歸調(diào)用,由其他進(jìn)程拋出互斥鎖解鎖異常時(shí),重試執(zhí)行 MutexExec(mutexKey: mutexKey, action: action, recursive: true); } finally { //釋放鎖,讓其他進(jìn)程(或線程)得以繼續(xù)執(zhí)行 mut.ReleaseMutex(); } } } } #region 測試寫文件的代碼 static int LogCount = 500; static int WritedCount = 0; static int FailedCount = 0; static void WriteLog(string logFilePath) { try { 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) { Console.WriteLine(ex.Message); FailedCount++; } } #endregion }
運(yùn)行結(jié)果:
到此這篇關(guān)于C#在復(fù)雜多線程環(huán)境下使用讀寫鎖同步寫入文件的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#使用checkedListBox1控件鏈接數(shù)據(jù)庫的方法示例
這篇文章主要介紹了C#使用checkedListBox1控件鏈接數(shù)據(jù)庫的方法,結(jié)合具體實(shí)例形式分析了數(shù)據(jù)庫的創(chuàng)建及checkedListBox1控件連接數(shù)據(jù)庫的相關(guān)操作技巧,需要的朋友可以參考下2017-06-06用?FieldMask?提高?C#?gRpc?的服務(wù)性能
這篇文章主要介紹了用?FieldMask?提高?C#?gRpc?的服務(wù)性能,FieldMask?是一個(gè)?protobuf?消息,包含一個(gè)名為?paths?的字段,用于指定用于指定讀取操作返回或更新操作修改的字,下文詳細(xì)內(nèi)容,需要的朋友可以參考一下2022-03-03npoi2.0將datatable對象轉(zhuǎn)換為excel2007示例
這篇文章主要介紹了npoi2.0將datatable對象轉(zhuǎn)換為excel2007示例的相關(guān)資料2014-04-04一篇文章教會(huì)你用Unity制作網(wǎng)格地圖生成組件
網(wǎng)格地圖這個(gè)功能在策略型游戲中應(yīng)用比較廣泛,基本情況下會(huì)將地圖分割成正方形網(wǎng)格或者六邊形網(wǎng)格,這篇文章主要給大家介紹了如何通過一篇文章學(xué)會(huì)用Unity制作網(wǎng)格地圖生成組件的相關(guān)資料,需要的朋友可以參考下2021-08-08C#解決SQlite并發(fā)異常問題的方法(使用讀寫鎖)
這篇文章主要介紹了C#解決SQlite并發(fā)異常問題的方法,通過使用讀寫鎖達(dá)到多線程安全訪問,進(jìn)而解決SQLite并發(fā)異常的問題,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07