C#多線程系列之進程同步Mutex類
Mutex 中文為互斥,Mutex 類叫做互斥鎖。它還可用于進程間同步的同步基元。
Mutex 跟 lock 相似,但是 Mutex 支持多個進程。Mutex 大約比 lock 慢 20 倍。
互斥鎖(Mutex),用于多線程中防止兩條線程同時對一個公共資源進行讀寫的機制。
Windows 操作系統(tǒng)中,Mutex 同步對象有兩個狀態(tài):
- signaled:未被任何對象擁有;
- nonsignaled:被一個線程擁有;
Mutex 只能在獲得鎖的線程中,釋放鎖。
構造函數(shù)和方法
Mutex 類其構造函數(shù)如下:
| 構造函數(shù) | 說明 |
|---|---|
| Mutex() | 使用默認屬性初始化 Mutex類的新實例。 |
| Mutex(Boolean) | 使用 Boolean 值(指示調(diào)用線程是否應具有互斥體的初始所有權)初始化 Mutex 類的新實例。 |
| Mutex(Boolean, String) | 使用 Boolean 值(指示調(diào)用線程是否應具有互斥體的初始所有權以及字符串是否為互斥體的名稱)初始化 Mutex 類的新實例。 |
| Mutex(Boolean, String, Boolean) | 使用可指示調(diào)用線程是否應具有互斥體的初始所有權以及字符串是否為互斥體的名稱的 Boolean 值和當線程返回時可指示調(diào)用線程是否已賦予互斥體的初始所有權的 Boolean 值初始化 Mutex 類的新實例。 |
Mutex 對于進程同步有所幫助,例如其應用場景主要是控制系統(tǒng)只能運行一個此程序的實例。
Mutex 構造函數(shù)中的 String類型參數(shù) 叫做互斥量而互斥量是全局的操作系統(tǒng)對象。
Mutex 只要考慮實現(xiàn)進程間的同步,它會耗費比較多的資源,進程內(nèi)請考慮 Monitor/lock。
Mutex 的常用方法如下:
| 方法 | 說明 |
|---|---|
| Close() | 釋放由當前 WaitHandle 占用的所有資源。 |
| Dispose() | 釋放由 WaitHandle 類的當前實例占用的所有資源。 |
| OpenExisting(String) | 打開指定的已命名的互斥體(如果已經(jīng)存在)。 |
| ReleaseMutex() | 釋放 Mutex一次。 |
| TryOpenExisting(String, Mutex) | 打開指定的已命名的互斥體(如果已經(jīng)存在),并返回指示操作是否成功的值。 |
| WaitOne() | 阻止當前線程,直到當前 WaitHandle 收到信號。 |
| WaitOne(Int32) | 阻止當前線程,直到當前 WaitHandle 收到信號,同時使用 32 位帶符號整數(shù)指定時間間隔(以毫秒為單位)。 |
| WaitOne(Int32, Boolean) | 阻止當前線程,直到當前的 WaitHandle 收到信號為止,同時使用 32 位帶符號整數(shù)指定時間間隔,并指定是否在等待之前退出同步域。 |
| WaitOne(TimeSpan) | 阻止當前線程,直到當前實例收到信號,同時使用 TimeSpan 指定時間間隔。 |
| WaitOne(TimeSpan, Boolean) | 阻止當前線程,直到當前實例收到信號為止,同時使用 TimeSpan 指定時間間隔,并指定是否在等待之前退出同步域。 |
關于 Mutex 類,我們可以先通過幾個示例去了解它。
系統(tǒng)只能運行一個程序的實例
下面是一個示例,用于控制系統(tǒng)只能運行一個此程序的實例,不允許同時啟動多次。
class Program
{
// 第一個程序
const string name = "www.whuanle.cn";
private static Mutex m;
static void Main(string[] args)
{
// 本程序是否是 Mutex 的擁有者
bool firstInstance;
m = new Mutex(false,name,out firstInstance);
if (!firstInstance)
{
Console.WriteLine("程序已在運行!按下回車鍵退出!");
Console.ReadKey();
return;
}
Console.WriteLine("程序已經(jīng)啟動");
Console.WriteLine("按下回車鍵退出運行");
Console.ReadKey();
m.ReleaseMutex();
m.Close();
return;
}
}上面的代碼中,有些地方前面沒有講,沒關系,我們運行一下生成的程序先。

解釋一下上面的示例
Mutex 的工作原理:
當兩個或兩個以上的線程同時訪問共享資源時,操作系統(tǒng)需要一個同步機制來確保每次只有一個線程使用資源。
Mutex 是一種同步基元,Mutex 僅向一個線程授予獨占訪問共享資源的權限。這個權限依據(jù)就是 互斥體,當一個線程獲取到互斥體后,其它線程也在試圖獲取互斥體時,就會被掛起(阻塞),直到第一個線程釋放互斥體。
對應我們上一個代碼示例中,實例化 Mutex 類的構造函數(shù)如下:
m = new Mutex(false,name,out firstInstance);
其構造函數(shù)原型如下:
public Mutex (bool initiallyOwned, string name, out bool createdNew);
前面我們提出過,Mutex 對象有兩種狀態(tài),signaled 和 nonsignaled。
通過 new 來實例化 Mutex 類,會檢查系統(tǒng)中此互斥量 name 是否已經(jīng)被使用,如果沒有被使用,則會創(chuàng)建 name 互斥量并且此線程擁有此互斥量的使用權;此時 createdNew == true。
那么 initiallyOwned ,它的作用是是否允許線程是否能夠獲取到此互斥量的初始化所有權。因為我們希望只有一個程序能夠在后臺運行,因此我們要設置為 false。
驅(qū)動開發(fā)中關于Mutex :https://docs.microsoft.com/zh-cn/windows-hardware/drivers/kernel/introduction-to-mutex-objects
對了, Mutex 的 參數(shù)中,name 是非常有講究的。
在運行終端服務的服務器上,命名系統(tǒng) mutex 可以有兩個級別的可見性。
- 如果其名稱以前綴 "Global" 開頭,則 mutex 在所有終端服務器會話中可見。
- 如果其名稱以前綴 "Local" 開頭,則 mutex 僅在創(chuàng)建它的終端服務器會話中可見。 在這種情況下,可以在服務器上的其他每個終端服務器會話中存在具有相同名稱的單獨 mutex。
如果在創(chuàng)建已命名的 mutex 時未指定前綴,則采用前綴 "Local"。 在終端服務器會話中,兩個互斥體的名稱只是它們的前綴不同,它們都是對終端服務器會話中的所有進程都可見。
也就是說,前綴名稱 "Global" 和 "Local" 描述互斥體名稱相對于終端服務器會話的作用域,而不是相對于進程。
請參考:
https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.mutex?view=netcore-3.1#methods
http://www.dbjr.com.cn/article/237313.htm
接替運行
這里要實現(xiàn),當同時點擊一個程序時,只能有一個實例A可以運行,其它實例進入等待隊列,等待A運行完畢后,然后繼續(xù)運行隊列中的下一個實例。
我們將每個程序比作一個人,模擬一個廁所坑位,每次只能有一個人上廁所,其他人需要排隊等候。
使用 WaitOne() 方法來等待別的進程釋放互斥量,即模擬排隊;ReleaseMutex() 方法解除對坑位的占用。
class Program
{
// 第一個程序
const string name = "www.whuanle.cn";
private static Mutex m;
static void Main(string[] args)
{
// wc 還有沒有位置
bool firstInstance;
m = new Mutex(true,name,out firstInstance);
// 已經(jīng)有人在上wc
if (!firstInstance)
{
// 等待運行的實例退出,此進程才能運行。
Console.WriteLine("排隊等待");
m.WaitOne();
GoWC();
return;
}
GoWC();
return;
}
private static void GoWC()
{
Console.WriteLine(" 開始上wc");
Thread.Sleep(1000);
Console.WriteLine(" 開門");
Thread.Sleep(1000);
Console.WriteLine(" 關門");
Thread.Sleep(1000);
Console.WriteLine(" xxx");
Thread.Sleep(1000);
Console.WriteLine(" 開門");
Thread.Sleep(1000);
Console.WriteLine(" 離開wc");
m.ReleaseMutex();
Thread.Sleep(1000);
Console.WriteLine(" 洗手");
}
}
此時,我們使用了
m = new Mutex(true,name,out firstInstance);
一個程序結束后,要允許其它線程能夠創(chuàng)建 Mutex 對象獲取互斥量,需要將構造函數(shù)的第一個參數(shù)設置為 true。
你也可以改成 false,看看會報什么異常。
你可以使用 WaitOne(Int32) 來設置等待時間,單位是毫秒,超過這個時間就不排隊了,去別的地方上廁所。
為了避免出現(xiàn)問題,請考慮在 finally 塊中執(zhí)行 m.ReleaseMutex()。
進程同步示例
這里我們實現(xiàn)一個這樣的場景:
父進程 Parent 啟動子進程 Children ,等待子進程 Children 執(zhí)行完畢,子進程退出,父進程退出。
新建一個 .NET Core 控制臺項目,名稱為 Children,其 Progarm 中的代碼如下
using System;
using System.Threading;
namespace Children
{
class Program
{
const string name = "進程同步示例";
private static Mutex m;
static void Main(string[] args)
{
Console.WriteLine("子進程被啟動...");
bool firstInstance;
// 子進程創(chuàng)建互斥體
m = new Mutex(true, name, out firstInstance);
// 按照我們設計的程序,創(chuàng)建一定是成功的
if (firstInstance)
{
Console.WriteLine("子線程執(zhí)行任務");
DoWork();
Console.WriteLine("子線程任務完成");
// 釋放互斥體
m.ReleaseMutex();
// 結束程序
return;
}
else
{
Console.WriteLine("莫名其妙的異常,直接退出");
}
}
private static void DoWork()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("子線程工作中");
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
}
}然后發(fā)布或生成項目,打開程序文件位置,復制線程文件路徑。
創(chuàng)建一個新項目,名為 Parent 的 .NET Core 控制臺,其 Program 中的代碼如下:
using System;
using System.Diagnostics;
using System.Threading;
namespace Parent
{
class Program
{
const string name = "進程同步示例";
private static Mutex m;
static void Main(string[] args)
{
// 晚一些再執(zhí)行,我錄屏要對正窗口位置
Thread.Sleep(TimeSpan.FromSeconds(3));
Console.WriteLine("父進程啟動!");
new Thread(() =>
{
// 啟動子進程
Process process = new Process();
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = false;
process.StartInfo.WorkingDirectory = @"../../../ConsoleApp9\Children\bin\Debug\netcoreapp3.1";
process.StartInfo.FileName = @"../../../ConsoleApp9\Children\bin\Debug\netcoreapp3.1\Children.exe";
process.Start();
process.WaitForExit();
}).Start();
// 子進程啟動需要一點時間
Thread.Sleep(TimeSpan.FromSeconds(1));
// 獲取互斥體
bool firstInstance;
m = new Mutex(true, name, out firstInstance);
// 說明子進程還在運行
if (!firstInstance)
{
// 等待子進程運行結束
Console.WriteLine("等待子進程運行結束");
m.WaitOne();
Console.WriteLine("子進程運行結束,程序?qū)⒃?秒后自動退出");
m.ReleaseMutex();
Thread.Sleep(TimeSpan.FromSeconds(3));
return;
}
}
}
}請將 Children 項目的程序文件路徑,替換到 Parent 項目啟動子進程的那部分字符串中。
然后啟動 Parent.exe,可以觀察到如下圖的運行過程:

另外
構造函數(shù)中,如果為 name 指定 null 或空字符串,則將創(chuàng)建一個本地 Mutex 對象,只會在進程內(nèi)有效。
Mutex 有些使用方法比較隱晦,可以參考 https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.mutex.-ctor?view=netcore-3.1#System_Threading_Mutex__ctor_System_Boolean_
另外打開互斥體,請參考
https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.mutex.openexisting?view=netcore-3.1
https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.mutex.tryopenexisting?view=netcore-3.1
到此這篇關于C#多線程系列之進程同步Mutex類的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
C#ComboBox控件“設置 DataSource 屬性后無法修改項集合”的解決方法
這篇文章主要介紹了C#ComboBox控件“設置 DataSource 屬性后無法修改項集合”的解決方法 ,需要的朋友可以參考下2019-04-04

