C#多線程(4):程序同步Mutex類
目錄
Mutex 類
構造函數和方法
系統隻能運作一個程式的執行個體
解釋一下上面的示例
接替運作
程序同步示例
另外
Mutex 中文為互斥,Mutex 類叫做互斥鎖。它還可用于程序間同步的同步基元。
Mutex 跟 lock 相似,但是 Mutex 支援多個程序。Mutex 大約比 lock 慢 20 倍。
互斥鎖(Mutex),用于多線程中防止兩條線程同時對一個公共資源進行讀寫的機制。
Windows 作業系統中,Mutex 同步對象有兩個狀态:
signaled:未被任何對象擁有;
nonsignaled:被一個線程擁有;
Mutex 隻能在獲得鎖的線程中,釋放鎖。
Mutex 類其構造函數如下:
構造函數 說明
Mutex() 使用預設屬性初始化 Mutex類的新執行個體。
Mutex(Boolean) 使用 Boolean 值(訓示調用線程是否應具有互斥體的初始所有權)初始化 Mutex 類的新執行個體。
Mutex(Boolean, String) 使用 Boolean 值(訓示調用線程是否應具有互斥體的初始所有權以及字元串是否為互斥體的名稱)初始化 Mutex 類的新執行個體。
Mutex(Boolean, String, Boolean) 使用可訓示調用線程是否應具有互斥體的初始所有權以及字元串是否為互斥體的名稱的 Boolean 值和當線程傳回時可訓示調用線程是否已賦予互斥體的初始所有權的 Boolean 值初始化 Mutex 類的新執行個體。
Mutex 對于程序同步有所幫助,例如其應用場景主要是控制系統隻能運作一個此程式的執行個體。
Mutex 構造函數中的 String類型參數 叫做互斥量而互斥量是全局的作業系統對象。
Mutex 隻要考慮實作程序間的同步,它會耗費比較多的資源,程序内請考慮 Monitor/lock。
Mutex 的常用方法如下:
方法 說明
Close() 釋放由目前 WaitHandle 占用的所有資源。
Dispose() 釋放由 WaitHandle 類的目前執行個體占用的所有資源。
OpenExisting(String) 打開指定的已命名的互斥體(如果已經存在)。
ReleaseMutex() 釋放 Mutex一次。
TryOpenExisting(String, Mutex) 打開指定的已命名的互斥體(如果已經存在),并傳回訓示操作是否成功的值。
WaitOne() 阻止目前線程,直到目前 WaitHandle 收到信号。
WaitOne(Int32) 阻止目前線程,直到目前 WaitHandle 收到信号,同時使用 32 位帶符号整數指定時間間隔(以毫秒為機關)。
WaitOne(Int32, Boolean) 阻止目前線程,直到目前的 WaitHandle 收到信号為止,同時使用 32 位帶符号整數指定時間間隔,并指定是否在等待之前退出同步域。
WaitOne(TimeSpan) 阻止目前線程,直到目前執行個體收到信号,同時使用 TimeSpan 指定時間間隔。
WaitOne(TimeSpan, Boolean) 阻止目前線程,直到目前執行個體收到信号為止,同時使用 TimeSpan 指定時間間隔,并指定是否在等待之前退出同步域。
關于 Mutex 類,我們可以先通過幾個示例去了解它。
下面是一個示例,用于控制系統隻能運作一個此程式的執行個體,不允許同時啟動多次。
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("程式已在運作!按下Enter鍵退出!");
Console.ReadKey();
return;
}
Console.WriteLine("程式已經啟動");
Console.WriteLine("按下Enter鍵退出運作");
Console.ReadKey();
m.ReleaseMutex();
m.Close();
return;
}
}
上面的代碼中,有些地方前面沒有講,沒關系,我們運作一下生成的程式先。
Mutex 的工作原理:
當兩個或兩個以上的線程同時通路共享資源時,作業系統需要一個同步機制來確定每次隻有一個線程使用資源。
Mutex 是一種同步基元,Mutex 僅向一個線程授予獨占通路共享資源的權限。這個權限依據就是 互斥體,當一個線程擷取到互斥體後,其它線程也在試圖擷取互斥體時,就會被挂起(阻塞),直到第一個線程釋放互斥體。
對應我們上一個代碼示例中,執行個體化 Mutex 類的構造函數如下:
m = new Mutex(false,name,out firstInstance);
其構造函數原型如下:
public Mutex (bool initiallyOwned, string name, out bool createdNew);
前面我們提出過,Mutex 對象有兩種狀态,signaled 和 nonsignaled。
通過 new 來執行個體化 Mutex 類,會檢查系統中此互斥量 name 是否已經被使用,如果沒有被使用,則會建立 name 互斥量并且此線程擁有此互斥量的使用權;此時 createdNew == true。
那麼 initiallyOwned ,它的作用是是否允許線程是否能夠擷取到此互斥量的初始化所有權。因為我們希望隻有一個程式能夠在背景運作,是以我們要設定為 false。
驅動開發中關于Mutex :
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/kernel/introduction-to-mutex-objects對了, Mutex 的 參數中,name 是非常有講究的。
在運作終端服務的伺服器上,命名系統 mutex 可以有兩個級别的可見性。
如果其名稱以字首 "Global" 開頭,則 mutex 在所有終端伺服器會話中可見。
如果其名稱以字首 "Local" 開頭,則 mutex 僅在建立它的終端伺服器會話中可見。 在這種情況下,可以在伺服器上的其他每個終端伺服器會話中存在具有相同名稱的單獨 mutex。
如果在建立已命名的 mutex 時未指定字首,則采用字首 "Local"。 在終端伺服器會話中,兩個互斥體的名稱隻是它們的字首不同,它們都是對終端伺服器會話中的所有程序都可見。
也就是說,字首名稱 "Global" 和 "Local" 描述互斥體名稱相對于終端伺服器會話的作用域,而不是相對于程序。
請參考:
https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.mutex?view=netcore-3.1#methods https://www.cnblogs.com/suntp/p/8258488.html這裡要實作,當同時點選一個程式時,隻能有一個執行個體A可以運作,其它執行個體進入等待隊列,等待A運作完畢後,然後繼續運作隊列中的下一個執行個體。
我們将每個程式比作一個人,模拟一個廁所坑位,每次隻能有一個人上廁所,其他人需要排隊等候。
使用 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);
// 已經有人在上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);
一個程式結束後,要允許其它線程能夠建立 Mutex 對象擷取互斥量,需要将構造函數的第一個參數設定為 true。
你也可以改成 false,看看會報什麼異常。
你可以使用 WaitOne(Int32) 來設定等待時間,機關是毫秒,超過這個時間就不排隊了,去别的地方上廁所。
為了避免出現問題,請考慮在 finally 塊中執行 m.ReleaseMutex()。
這裡我們實作一個這樣的場景:
父程序 Parant 啟動子程序 Children ,等待子程序 Children 執行完畢,子程序退出,父程序退出。
建立一個 .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;
// 子程序建立互斥體
m = new Mutex(true, name, out firstInstance);
// 按照我們設計的程式,建立一定是成功的
if (firstInstance)
{
Console.WriteLine("子線程執行任務");
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));
}
}
}
}
然後釋出或生成項目,打開程式檔案位置,複制線程檔案路徑。
建立一個新項目,名為 Parant 的 .NET Core 控制台,其 Program 中的代碼如下:
using System.Diagnostics;
namespace Parant
class Program
{
const string name = "程序同步示例";
private static Mutex m;
static void Main(string[] args)
{
// 晚一些再執行,我錄屏要對正視窗位置
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("子程序運作結束,程式将在3秒後自動退出");
m.ReleaseMutex();
Thread.Sleep(TimeSpan.FromSeconds(3));
return;
}
}
}
請将 Children 項目的程式檔案路徑,替換到 Parant 項目啟動子程序的那部分字元串中。
然後啟動 Parant.exe,可以觀察到如下圖的運作過程:
構造函數中,如果為 name 指定 null 或空字元串,則将建立一個本地 Mutex 對象,隻會在程序内有效。
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到目前為止,我們學習了排他鎖 lock、Monitor、Mutex。下一篇我們将來學習非排他鎖定結構的Semaphore和SemaphoreSlim 。
原文位址
https://www.cnblogs.com/whuanle/p/12726724.html