C#在復(fù)雜多線程環(huán)境下使用讀寫(xiě)鎖同步寫(xiě)入文件
代碼一:
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ù),迭代寫(xiě)入N個(gè)日志
SumLogCount += LogCount;
ThreadPool.QueueUserWorkItem((obj) =>
{
Parallel.For(0, LogCount, e =>
{
WriteLog();
});
});
//在新的線程里,添加N個(gè)寫(xiě)入日志的任務(wù)到線程池
SumLogCount += LogCount;
var thread1 = new Thread(() =>
{
Parallel.For(0, LogCount, e =>
{
ThreadPool.QueueUserWorkItem((subObj) =>
{
WriteLog();
});
});
});
thread1.IsBackground = false;
thread1.Start();
//添加N個(gè)寫(xiě)入日志的任務(wù)到線程池
SumLogCount += LogCount;
Parallel.For(0, LogCount, e =>
{
ThreadPool.QueueUserWorkItem((obj) =>
{
WriteLog();
});
});
//在新的線程里,迭代寫(xiě)入N個(gè)日志
SumLogCount += LogCount;
var thread2 = new Thread(() =>
{
Parallel.For(0, LogCount, e =>
{
WriteLog();
});
});
thread2.IsBackground = false;
thread2.Start();
//在當(dāng)前線程里,迭代寫(xiě)入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();
}
}
//讀寫(xiě)鎖,當(dāng)資源處于寫(xiě)入模式時(shí),其他線程寫(xiě)入需要等待本次寫(xiě)入結(jié)束之后才能繼續(xù)寫(xiě)入
static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
static void WriteLog()
{
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();
}
}
}運(yùn)行結(jié)果:
復(fù)雜多線程環(huán)境下使用讀寫(xiě)鎖,全部日志成功寫(xiě)入了日志文件,由ThreadId和DateTime可以看出是由不同的線程同步寫(xiě)入。

代碼二:
class Program
{
static void Main(string[] args)
{
#region 簡(jiǎn)單使用
//var mutexKey = MutexExample.GetFilePathMutexKey("文件路徑");
//MutexExample.MutexExec(mutexKey, () =>
//{
// Console.WriteLine("需要進(jìn)程同步執(zhí)行的代碼");
//});
#endregion
#region 測(cè)試代碼
//D:\Winform\多線程\多線程_互斥信號(hào)量\bin\Debug\TEST.LOG
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.log").ToUpper();
var mutexKey = MutexExample.GetFilePathMutexKey(filePath);
//同時(shí)開(kāi)啟N個(gè)寫(xiě)入線程
Parallel.For(0, LogCount, e =>
{
//沒(méi)使用互斥鎖操作寫(xiě)入,大量寫(xiě)入錯(cuò)誤;FileStream包含F(xiàn)ileShare的構(gòu)造函數(shù)也僅實(shí)現(xiàn)了進(jìn)程內(nèi)的線程同步,多進(jìn)程同時(shí)寫(xiě)入時(shí)也會(huì)出錯(cuò)
//WriteLog(filePath);
//使用互斥鎖操作寫(xiě)入,由于同一時(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)過(guò)測(cè)試并上線運(yùn)行,可直接使用</remarks>
public static class MutexExample
{
/// <summary>
/// 進(jìn)程間同步執(zhí)行的簡(jiǎn)單例子
/// </summary>
/// <param name="action">同步處理代碼</param>
/// <param name="mutexKey">操作系統(tǒng)級(jí)的同步鍵
/// (如果將 name 指定為 null 或空字符串,則創(chuàng)建一個(gè)局部互斥體。
/// 如果名稱以前綴“Global\”開(kāi)頭,則 mutex 在所有終端服務(wù)器會(huì)話中均為可見(jiàn)。
/// 如果名稱以前綴“Local\”開(kāi)頭,則 mutex 僅在創(chuàng)建它的終端服務(wù)器會(huì)話中可見(jiàn)。
/// 如果創(chuàng)建已命名 mutex 時(shí)不指定前綴,則它將采用前綴“Local\”。)</param>
/// <remarks>不重試且不考慮異常情況處理的簡(jiǎn)單例子</remarks>
[Obsolete(error: false, message: "請(qǐng)使用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>
/// 獲取文件名對(duì)應(yīng)的進(jìn)程同步鍵
/// </summary>
/// <param name="filePath">文件路徑(請(qǐng)注意大小寫(xiě)及空格)</param>
/// <returns>進(jìn)程同步鍵(互斥體名稱)</returns>
public static string GetFilePathMutexKey(string filePath)
{
//生成文件對(duì)應(yīng)的同步鍵,可自定義格式(互斥體名稱對(duì)特殊字符支持不友好,遂轉(zhuǎn)換為BASE64格式字符串)
var fileKey = Convert.ToBase64String(Encoding.Default.GetBytes(string.Format(@"FILE\{0}", filePath)));
//轉(zhuǎn)換為操作系統(tǒng)級(jí)的同步鍵
var mutexKey = string.Format(@"Global\{0}", fileKey);
return mutexKey;
}
/// <summary>
/// 進(jìn)程間同步執(zhí)行
/// </summary>
/// <param name="mutexKey">操作系統(tǒng)級(jí)的同步鍵
/// (如果將 name 指定為 null 或空字符串,則創(chuàng)建一個(gè)局部互斥體。
/// 如果名稱以前綴“Global\”開(kāi)頭,則 mutex 在所有終端服務(wù)器會(huì)話中均為可見(jiàn)。
/// 如果名稱以前綴“Local\”開(kāi)頭,則 mutex 僅在創(chuàng)建它的終端服務(wù)器會(huì)話中可見(jiàn)。
/// 如果創(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)級(jí)的同步鍵
/// (如果將 name 指定為 null 或空字符串,則創(chuàng)建一個(gè)局部互斥體。
/// 如果名稱以前綴“Global\”開(kāi)頭,則 mutex 在所有終端服務(wù)器會(huì)話中均為可見(jiàn)。
/// 如果名稱以前綴“Local\”開(kāi)頭,則 mutex 僅在創(chuàng)建它的終端服務(wù)器會(huì)話中可見(jiàn)。
/// 如果創(chuàng)建已命名 mutex 時(shí)不指定前綴,則它將采用前綴“Local\”。)</param>
/// <param name="action">同步處理操作</param>
/// <param name="recursive">指示當(dāng)前調(diào)用是否為遞歸處理,遞歸處理時(shí)檢測(cè)到異常則拋出異常,避免進(jìn)入無(wú)限遞歸</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í)間過(guò)短,則可能同時(shí)聲明了多個(gè)名稱相同的互斥體,并且同名的多個(gè)互斥體之間并不同步,高并發(fā)用戶請(qǐng)另行處理
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)程已上鎖且沒(méi)有正常釋放互斥鎖時(shí)(譬如進(jìn)程忽然關(guān)閉或退出),則會(huì)拋出AbandonedMutexException異常
catch (AbandonedMutexException ex)
{
//避免進(jìn)入無(wú)限遞歸
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 測(cè)試寫(xiě)文件的代碼
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)境下使用讀寫(xiě)鎖同步寫(xiě)入文件的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- C#多線程中的互斥鎖Mutex
- C#多線程死鎖介紹與案例代碼
- C#使用讀寫(xiě)鎖解決多線程并發(fā)問(wèn)題
- C#多線程系列之讀寫(xiě)鎖
- C#多線程系列之多線程鎖lock和Monitor
- c#多線程之間的排他鎖的實(shí)現(xiàn)
- C#筆試題之同線程Lock語(yǔ)句遞歸不會(huì)死鎖
- C#多線程中如何運(yùn)用互斥鎖Mutex
- C#使用讀寫(xiě)鎖三行代碼簡(jiǎn)單解決多線程并發(fā)的問(wèn)題
- C#多線程編程中的鎖系統(tǒng)(四):自旋鎖
- C#多線程編程中的鎖系統(tǒng)(三)
- C#多線程編程中的鎖系統(tǒng)(二)
- C#多線程編程中的鎖系統(tǒng)基本用法
- C#多線程之線程鎖
相關(guān)文章
C#進(jìn)階系列 WebApi身份認(rèn)證解決方案推薦:Basic基礎(chǔ)認(rèn)證
下面小編就為大家?guī)?lái)一篇C#進(jìn)階系列 WebApi身份認(rèn)證解決方案推薦:Basic基礎(chǔ)認(rèn)證。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家。給大家一個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-03-03
C# 使用HttpClient上傳文件并附帶其他參數(shù)的步驟
這篇文章主要介紹了C# 使用HttpClient上傳文件并附帶其他參數(shù)的步驟,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下2020-12-12
C#數(shù)據(jù)結(jié)構(gòu)與算法揭秘五 棧和隊(duì)列
這節(jié)我們討論了兩種好玩的數(shù)據(jù)結(jié)構(gòu),棧和隊(duì)列2012-11-11
Unity?Shader編輯器工具類ShaderUtil?常用函數(shù)和用法實(shí)例詳解
Unity的Shader編輯器工具類ShaderUtil提供了一系列函數(shù),用于編譯、導(dǎo)入和管理著色器,這篇文章主要介紹了Unity?Shader編輯器工具類ShaderUtil?常用函數(shù)和用法,需要的朋友可以參考下2023-08-08
c#中多線程訪問(wèn)winform控件的若干問(wèn)題小結(jié)
大部分情況下都會(huì)碰到使用多線程控制界面上控件信息的問(wèn)題。然而我們并不能用傳統(tǒng)方法來(lái)解決這個(gè)問(wèn)題,下面我將詳細(xì)的介紹2013-10-10
C#實(shí)現(xiàn)可緩存網(wǎng)頁(yè)到本地的反向代理工具實(shí)例
這篇文章主要介紹了C#實(shí)現(xiàn)可緩存網(wǎng)頁(yè)到本地的反向代理工具,實(shí)例分析了C#實(shí)現(xiàn)反向代理的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
C#?利用Autofac批量接口注入依賴的問(wèn)題小結(jié)
這篇文章主要介紹了C#?利用Autofac批量接口注入依賴的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
C#實(shí)現(xiàn)Winform無(wú)邊框移動(dòng)的方法
這篇文章主要介紹了C#實(shí)現(xiàn)Winform無(wú)邊框移動(dòng)的方法,涉及C#針對(duì)WinForm窗口操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09

