C#在復雜多線程環(huán)境下使用讀寫鎖同步寫入文件
更新時間:2022年04月18日 10:15:27 作者:農碼一生
這篇文章介紹了C#在復雜多線程環(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)
{
//往線程池里添加一個任務,迭代寫入N個日志
SumLogCount += LogCount;
ThreadPool.QueueUserWorkItem((obj) =>
{
Parallel.For(0, LogCount, e =>
{
WriteLog();
});
});
//在新的線程里,添加N個寫入日志的任務到線程池
SumLogCount += LogCount;
var thread1 = new Thread(() =>
{
Parallel.For(0, LogCount, e =>
{
ThreadPool.QueueUserWorkItem((subObj) =>
{
WriteLog();
});
});
});
thread1.IsBackground = false;
thread1.Start();
//添加N個寫入日志的任務到線程池
SumLogCount += LogCount;
Parallel.For(0, LogCount, e =>
{
ThreadPool.QueueUserWorkItem((obj) =>
{
WriteLog();
});
});
//在新的線程里,迭代寫入N個日志
SumLogCount += LogCount;
var thread2 = new Thread(() =>
{
Parallel.For(0, LogCount, e =>
{
WriteLog();
});
});
thread2.IsBackground = false;
thread2.Start();
//在當前線程里,迭代寫入N個日志
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();
}
}
//讀寫鎖,當資源處于寫入模式時,其他線程寫入需要等待本次寫入結束之后才能繼續(xù)寫入
static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
static void WriteLog()
{
try
{
//設置讀寫鎖為寫入模式獨占資源,其他寫入請求需要等待本次寫入結束之后才能繼續(xù)寫入
//注意:長時間持有讀線程鎖或寫線程鎖會使其他線程發(fā)生饑餓 (starve)。 為了得到最好的性能,需要考慮重新構造應用程序以將寫訪問的持續(xù)時間減少到最小。
//從性能方面考慮,請求進入寫入模式應該緊跟文件操作之前,在此處進入寫入模式僅是為了降低代碼復雜度
//因進入與退出寫入模式應在同一個try finally語句塊內,所以在請求進入寫入模式之前不能觸發(fā)異常,否則釋放次數大于請求次數將會觸發(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
{
//退出寫入模式,釋放資源占用
//注意:一次請求對應一次釋放
// 若釋放次數大于請求次數將會觸發(fā)異常[寫入鎖定未經保持即被釋放]
// 若請求處理完成后未釋放將會觸發(fā)異常[此模式不下允許以遞歸方式獲取寫入鎖定]
LogWriteLock.ExitWriteLock();
}
}
}運行結果:
復雜多線程環(huán)境下使用讀寫鎖,全部日志成功寫入了日志文件,由ThreadId和DateTime可以看出是由不同的線程同步寫入。

代碼二:
class Program
{
static void Main(string[] args)
{
#region 簡單使用
//var mutexKey = MutexExample.GetFilePathMutexKey("文件路徑");
//MutexExample.MutexExec(mutexKey, () =>
//{
// Console.WriteLine("需要進程同步執(zhí)行的代碼");
//});
#endregion
#region 測試代碼
//D:\Winform\多線程\多線程_互斥信號量\bin\Debug\TEST.LOG
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.log").ToUpper();
var mutexKey = MutexExample.GetFilePathMutexKey(filePath);
//同時開啟N個寫入線程
Parallel.For(0, LogCount, e =>
{
//沒使用互斥鎖操作寫入,大量寫入錯誤;FileStream包含F(xiàn)ileShare的構造函數也僅實現(xiàn)了進程內的線程同步,多進程同時寫入時也會出錯
//WriteLog(filePath);
//使用互斥鎖操作寫入,由于同一時間僅有一個線程操作,所以不會出錯
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>已在經過測試并上線運行,可直接使用</remarks>
public static class MutexExample
{
/// <summary>
/// 進程間同步執(zhí)行的簡單例子
/// </summary>
/// <param name="action">同步處理代碼</param>
/// <param name="mutexKey">操作系統(tǒng)級的同步鍵
/// (如果將 name 指定為 null 或空字符串,則創(chuàng)建一個局部互斥體。
/// 如果名稱以前綴“Global\”開頭,則 mutex 在所有終端服務器會話中均為可見。
/// 如果名稱以前綴“Local\”開頭,則 mutex 僅在創(chuàng)建它的終端服務器會話中可見。
/// 如果創(chuàng)建已命名 mutex 時不指定前綴,則它將采用前綴“Local\”。)</param>
/// <remarks>不重試且不考慮異常情況處理的簡單例子</remarks>
[Obsolete(error: false, message: "請使用MutexExec")]
public static void MutexExecEasy(string mutexKey, Action action)
{
//聲明一個已命名的互斥體,實現(xiàn)進程間同步;該命名互斥體不存在則自動創(chuàng)建,已存在則直接獲取
using (Mutex mut = new Mutex(false, mutexKey))
{
try
{
//上鎖,其他線程需等待釋放鎖之后才能執(zhí)行處理;若其他線程已經上鎖或優(yōu)先上鎖,則先等待其他線程執(zhí)行完畢
mut.WaitOne();
//執(zhí)行處理代碼(在調用WaitHandle.WaitOne至WaitHandle.ReleaseMutex的時間段里,只有一個線程處理,其他線程都得等待釋放鎖后才能執(zhí)行該代碼段)
action();
}
finally
{
//釋放鎖,讓其他進程(或線程)得以繼續(xù)執(zhí)行
mut.ReleaseMutex();
}
}
}
/// <summary>
/// 獲取文件名對應的進程同步鍵
/// </summary>
/// <param name="filePath">文件路徑(請注意大小寫及空格)</param>
/// <returns>進程同步鍵(互斥體名稱)</returns>
public static string GetFilePathMutexKey(string filePath)
{
//生成文件對應的同步鍵,可自定義格式(互斥體名稱對特殊字符支持不友好,遂轉換為BASE64格式字符串)
var fileKey = Convert.ToBase64String(Encoding.Default.GetBytes(string.Format(@"FILE\{0}", filePath)));
//轉換為操作系統(tǒng)級的同步鍵
var mutexKey = string.Format(@"Global\{0}", fileKey);
return mutexKey;
}
/// <summary>
/// 進程間同步執(zhí)行
/// </summary>
/// <param name="mutexKey">操作系統(tǒng)級的同步鍵
/// (如果將 name 指定為 null 或空字符串,則創(chuàng)建一個局部互斥體。
/// 如果名稱以前綴“Global\”開頭,則 mutex 在所有終端服務器會話中均為可見。
/// 如果名稱以前綴“Local\”開頭,則 mutex 僅在創(chuàng)建它的終端服務器會話中可見。
/// 如果創(chuàng)建已命名 mutex 時不指定前綴,則它將采用前綴“Local\”。)</param>
/// <param name="action">同步處理操作</param>
public static void MutexExec(string mutexKey, Action action)
{
MutexExec(mutexKey: mutexKey, action: action, recursive: false);
}
/// <summary>
/// 進程間同步執(zhí)行
/// </summary>
/// <param name="mutexKey">操作系統(tǒng)級的同步鍵
/// (如果將 name 指定為 null 或空字符串,則創(chuàng)建一個局部互斥體。
/// 如果名稱以前綴“Global\”開頭,則 mutex 在所有終端服務器會話中均為可見。
/// 如果名稱以前綴“Local\”開頭,則 mutex 僅在創(chuàng)建它的終端服務器會話中可見。
/// 如果創(chuàng)建已命名 mutex 時不指定前綴,則它將采用前綴“Local\”。)</param>
/// <param name="action">同步處理操作</param>
/// <param name="recursive">指示當前調用是否為遞歸處理,遞歸處理時檢測到異常則拋出異常,避免進入無限遞歸</param>
private static void MutexExec(string mutexKey, Action action, bool recursive)
{
//聲明一個已命名的互斥體,實現(xiàn)進程間同步;該命名互斥體不存在則自動創(chuàng)建,已存在則直接獲取
//initiallyOwned: false:默認當前線程并不擁有已存在互斥體的所屬權,即默認本線程并非為首次創(chuàng)建該命名互斥體的線程
//注意:并發(fā)聲明同名的命名互斥體時,若間隔時間過短,則可能同時聲明了多個名稱相同的互斥體,并且同名的多個互斥體之間并不同步,高并發(fā)用戶請另行處理
using (Mutex mut = new Mutex(initiallyOwned: false, name: mutexKey))
{
try
{
//上鎖,其他線程需等待釋放鎖之后才能執(zhí)行處理;若其他線程已經上鎖或優(yōu)先上鎖,則先等待其他線程執(zhí)行完畢
mut.WaitOne();
//執(zhí)行處理代碼(在調用WaitHandle.WaitOne至WaitHandle.ReleaseMutex的時間段里,只有一個線程處理,其他線程都得等待釋放鎖后才能執(zhí)行該代碼段)
action();
}
//當其他進程已上鎖且沒有正常釋放互斥鎖時(譬如進程忽然關閉或退出),則會拋出AbandonedMutexException異常
catch (AbandonedMutexException ex)
{
//避免進入無限遞歸
if (recursive)
throw ex;
//非遞歸調用,由其他進程拋出互斥鎖解鎖異常時,重試執(zhí)行
MutexExec(mutexKey: mutexKey, action: action, recursive: true);
}
finally
{
//釋放鎖,讓其他進程(或線程)得以繼續(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
}運行結果:

到此這篇關于C#在復雜多線程環(huán)境下使用讀寫鎖同步寫入文件的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
C#進階系列 WebApi身份認證解決方案推薦:Basic基礎認證
下面小編就為大家?guī)硪黄狢#進階系列 WebApi身份認證解決方案推薦:Basic基礎認證。小編覺得挺不錯的,現(xiàn)在分享給大家。給大家一個參考。一起跟隨小編過來看看吧2016-03-03
Unity?Shader編輯器工具類ShaderUtil?常用函數和用法實例詳解
Unity的Shader編輯器工具類ShaderUtil提供了一系列函數,用于編譯、導入和管理著色器,這篇文章主要介紹了Unity?Shader編輯器工具類ShaderUtil?常用函數和用法,需要的朋友可以參考下2023-08-08

