Mutex,中文譯為互斥體,在.net中也是作為一種線程或進程之間的互斥體存在。即在同一時刻,一個共享資源只允許被某一個線程或進程訪問,其他線程或進程需要等待(直至獲取互斥鎖為止)。
Mutex的使用方式與Monitor很相似,但絕不相同。Monitor支持線程間並發同步,Mutex不但支持線程也支持進程間並發同步。
Mutex有許多需要注意的地方,如果稍不注意,則你會被坑慘!
接下來我們使用控制台和webapi項目各自做測試。
一、使用控制台測試進程間的同步
測試代碼如下:
[STAThread] static void Main(string[] args) { //使用指定名稱為MName的互斥體 //如果獲取到名稱為MName的互斥體,則flag為true,否則為false。flag作用很大,如果是指定了互斥體的名稱,一定要用flag去判斷是否互斥。 Mutex mutex = new Mutex(true, "MName", out bool flag); if (flag) { Console.WriteLine("1.獲取互斥鎖,開始運行"); Thread.Sleep(5000); Console.WriteLine("2.釋放互斥鎖!"); mutex.ReleaseMutex(); Console.WriteLine("3.開始等待,5秒后超時!"); if (mutex.WaitOne(5000)) Console.WriteLine("6.獲取互斥鎖,開始運行!"); else Console.WriteLine("6.超時釋放互斥鎖!"); Thread.Sleep(1000); } else { Console.WriteLine("1.未獲取互斥鎖,開始等待!"); mutex.WaitOne(); Console.WriteLine("4.獲取互斥鎖,開始運行!"); Console.WriteLine("5.釋放互斥鎖!"); mutex.ReleaseMutex(); } Console.ReadKey(); }
同時打開兩個程序的運行結果如下所示:

進程間的並發同步需要指定互斥體的名稱,只要是同一個名稱,它們就能互斥。
Mutex的WaitOne()方法是阻塞當前線程並等待獲取互斥體。ReleaseMutex()方法是釋放當前線程擁有的互斥體,如果當前進程或線程沒有擁有互斥體Mutex,則會拋出System.ApplicationException異常,這點需要注意。
WaitOne和ReleaseMutex方法最好成對出現,反正單個方法兩兩不相見。
多個ReleaseMutex方法一起運行的異常見以下圖片:

這個問題可以看ReleaseMutex方法的定義說明:
/// <summary>Releases the <see cref="T:System.Threading.Mutex"></see> once.</summary> /// <exception cref="T:System.ApplicationException">The calling thread does not own the mutex.</exception> /// <exception cref="T:System.ObjectDisposedException">The current instance has already been disposed.</exception> public void ReleaseMutex();
產生System.ApplicationException異常的原因就是當前被調用的線程已經不擁有該互斥體!
二、使用WebApi項目測試線程間的同步
測試代碼如下:
[HttpGet("[controller]/v1/api/[action]")] public IActionResult Test() { return Json(SynchronizationTest()); } //指定一個互斥體名稱 protected static string MutexName => "MName"; public ResponseModel SynchronizationTest() { ResponseModel rc = new ResponseModel(0, "初始化"); Mutex mutex = new Mutex(true, MutexName, out bool flag); try { //如果沒有獲取到互斥體,則等待,3秒后超時。如果獲取到了互斥體,則flag=true,否則flag=false。一定要做flag判斷,否則會拋出異常 if(!flag) mutex.WaitOne(3000); int count = RedisHelper.Get(GoodsNumberKey).ToInt32(); if (count > 0) { RedisHelper.Set(GoodsNumberKey, "-1"); rc.SetMessage("重置成功!"); } else rc.SetMessage("已被重置,本次重置無效"); rc.SetCode(Thread.CurrentThread.ManagedThreadId); } catch (Exception ex) { _log.Error(ex); } finally { mutex.ReleaseMutex(); mutex.Close(); mutex.Dispose(); } return rc; }
接下來,我們使用如下代碼連續發送8次請求
HttpHelper.GetOnStringAsync($"{Url}/Shop/v1/api/Test", contentType: "application/json").ContinueWith(it => { Console.WriteLine(it.Result); });
測試的結果如下圖所示:

可以看見,我們連續發送8次請求,只有一次重置成功,其他的都因為互斥的關系而重置無效。
