1.引子
在前面2节,我们已经讨论了Lock语句和Monitor类,Locks 和 Monitors 确保 InProcess 线程的线程安全,即由应用程序本身生成的线程,即内部线程。但是,如果线程来自 OutProcess,即来自外部应用程序,那么 Locks 和 Monitors 将无法控制它们。而 Mutex 确保进程外线程的线程安全,即由外部应用程序生成的线程,即外部线程。
可能前面一段是说的不是很明白,什么是内部进程和外部进程呢?让我们首先创建一个控制台应用程序,然后将以下代码复制并粘贴到其中。
using System;
namespace MutexDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Application Is Running.......");
Console.ReadKey();
}
}
}
编译并运行,找到项目对应的bin文件夹,你会发现一个同名的可执行的exe文件,如图:
如果你运行三遍这个程序,那么就会打开三个实例窗口,因此三个外部线程同事访问着我们的应用程序。
如果你只想让一个程序实例访问内部代码,那么就需要使用Mutex类了,让我们先对上面的代码进行改动:
using System;
using System.Threading;
namespace MutexDemo
{
class Program
{
static void Main(string[] args)
{
using(Mutex mutex = new Mutex(false, "MutexDemo"))
{
//Checking if Other External Thread is Running
if(!mutex.WaitOne(5000, false))
{
Console.WriteLine("An Instance of the Application is Already Running");
Console.ReadKey();
return;
}
Console.WriteLine("Application Is Running.......");
Console.ReadKey();
}
}
}
}
再次运行三次这个程序:
可以看出只有第一个实例运行了最后两行,其它实例则无法进入。希望上面的例子能让你理解Mutex的用处。
2.Mutex介绍
Mutex 的工作方式类似于锁,即通过并发访问获得对共享资源的独占锁,但它可以跨多个进程工作。正如我们已经讨论过的,排他锁定基本上用于确保在任何给定时间点,只有一个线程可以进入临界区。
当两个或多个线程需要同时访问一个共享资源时,系统需要一种同步机制来保证一次只有一个线程使用该资源。Mutex 是一种同步机制,它只向一个外部线程授予对共享资源的独占访问权。如果一个线程获取互斥锁,则第二个想要获取该互斥锁的线程将被挂起,直到第一个线程释放该互斥锁。如果目前还不清楚,请不要担心,我会尝试通过一些示例来理解这一点。
2.1 Mutex构造函数
Mutex 是一个密封类,它继承自 WaitHandle 类。因为它是一个密封类,所以不可能有进一步的继承,即在 C# 中不能从这个密封的 Mutex 类派生任何类。
C# 中的 Mutex 类提供了以下五个构造函数,我们可以使用它们来创建 Mutex 类的实例。
Mutex():它使用默认属性初始化 Mutex 类的新实例。
Mutex(bool initiallyOwned):它使用布尔值初始化 Mutex 类的新实例,该值指示调用线程是否应具有互斥锁的初始所有权。
Mutex(bool initiallyOwned, string name):它用一个布尔值初始化 System.Threading.Mutex 类的一个新实例,该值指示调用线程是否应具有互斥锁的初始所有权,以及一个作为互斥锁名称的字符串.
Mutex(bool initiallyOwned, string name, out bool createdNew):它使用布尔值初始化 System.Threading.Mutex 类的新实例,该布尔值指示调用线程是否应具有互斥锁的初始所有权,一个字符串即名称互斥锁的属性,以及一个布尔值,当方法返回时,该值指示调用线程是否被授予互斥锁的初始所有权。
Mutex(bool initiallyOwned, string name, out bool createdNew, MutexSecurity mutexSecurity):它用一个布尔值初始化 System.Threading.Mutex 类的一个新实例,该值指示调用线程是否应该拥有互斥锁的初始所有权,一个字符串是互斥锁的名称,以及一个布尔变量,当方法返回时,它指示调用线程是否被授予互斥锁的初始所有权,以及要应用于指定互斥锁的访问控制安全性。
以下是 C# 中 Mutex 类的构造函数中使用的参数。
initiallyOwned:如果作为调用的结果创建了命名系统互斥锁,则为调用线程提供命名系统互斥锁的初始所有权;否则,假的。
name:互斥量的名称。如果该值为 null,则 Mutex 未命名。
createdNew:当此方法返回时,包含一个布尔值,如果创建了本地互斥锁(即,如果名称为 null 或空字符串)或创建了指定的命名系统互斥锁,则该布尔值为真;如果指定的命名系统互斥量已经存在,则为 false。此参数在未初始化的情况下传递。
mutexSecurity:一个 System.Security.AccessControl.MutexSecurity 对象,表示要应用于指定系统互斥锁的访问控制安全性。
2.2 Mutex成员函数
C# 中的 Mutex 类提供了以下方法。
OpenExisting(string name):此方法用于打开指定的命名互斥量(如果它已存在)。它返回一个代表命名系统互斥锁的对象。此处,参数名称指定要打开的系统互斥锁的名称。如果名称为空字符串,它将抛出 ArgumentException。- 或 - 名称超过 260 个字符。如果名称为空,它将抛出 ArgumentNullException。
OpenExisting(string name, MutexRights rights):此方法用于打开指定的命名互斥锁(如果它已经存在)并具有所需的安全访问权限。它返回一个代表命名系统互斥锁的对象。此处,参数名称指定要打开的系统互斥锁的名称。参数权限指定代表所需安全访问的枚举值的按位组合。
TryOpenExisting(string name, out Mutex result):该方法用于打开指定的命名互斥量,如果它已经存在,并返回一个值,指示操作是否成功。此处,参数名称指定要打开的系统互斥锁的名称。当此方法返回时,结果包含一个 Mutex 对象,如果调用成功,该对象表示指定的互斥锁,如果调用失败,则返回 null。此参数被视为未初始化。如果命名的互斥体被成功打开,它返回真;否则,假的。
TryOpenExisting(string name, MutexRights rights, out Mutex result):此方法用于打开指定的命名互斥锁(如果它已经存在),并具有所需的安全访问权限,并返回一个指示操作是否成功的值。此处,参数名称指定要打开的系统互斥锁的名称。参数权限指定代表所需安全访问的枚举值的按位组合。当此方法返回时,结果包含一个 Mutex 对象,如果调用成功,该对象表示指定的互斥锁,如果调用失败,则返回 null。此参数被视为未初始化。如果命名的互斥体被成功打开,它返回真;否则,假的。
ReleaseMutex():该方法用于释放一次Mutex。
GetAccessControl(): 此方法获取一个 System.Security.AccessControl.MutexSecurity 对象,该对象表示指定互斥体的访问控制安全性。它返回一个 System.Security.AccessControl.MutexSecurity 对象,表示指定互斥锁的访问控制安全性。
SetAccessControl(MutexSecurity mutexSecurity):此方法用于为指定的系统互斥锁设置访问控制安全性。参数 mutexSecurity 指定一个 System.Security.AccessControl.MutexSecurity 对象,该对象表示要应用于指定系统互斥锁的访问控制安全性。
3.举例分析
直接看下面的例子,关键地方给了注释。
internal class Example1
{
private static Mutex mutex = new Mutex();
public static void Run()
{
//Create multiple threads to understand Mutex
for (int i = 1; i <= 5; i++)
{
Thread threadObject = new Thread(MutexDemo)
{
Name = "Thread " + i
};
threadObject.Start();
}
Console.ReadKey();
}
static void MutexDemo()
{
Console.WriteLine(Thread.CurrentThread.Name + " Wants to Enter Critical Section for processing");
try
{
//Blocks the current thread until the current WaitOne method receives a signal.
//Wait until it is safe to enter.
mutex.WaitOne();
Console.WriteLine("Success: " + Thread.CurrentThread.Name + " is Processing now");
Thread.Sleep(2000);
Console.WriteLine("Exit: " + Thread.CurrentThread.Name + " is Completed its task");
}
finally
{
//Call the ReleaseMutex method to unblock so that other threads
//that are trying to gain ownership of the mutex can enter
mutex.ReleaseMutex();
}
}
}
因为每个线程都会被阻塞直到拥有mutex的所有权,在运行结束后,必须调用ReleaseMutex释放所有权,以保证其它线程可以进入。运行结果如下:
Thread 2 Wants to Enter Critical Section for processing
Thread 1 Wants to Enter Critical Section for processing
Thread 3 Wants to Enter Critical Section for processing
Thread 4 Wants to Enter Critical Section for processing
Thread 5 Wants to Enter Critical Section for processing
Success: Thread 2 is Processing now
Exit: Thread 2 is Completed its task
Success: Thread 1 is Processing now
Exit: Thread 1 is Completed its task
Success: Thread 3 is Processing now
Exit: Thread 3 is Completed its task
Success: Thread 4 is Processing now
Exit: Thread 4 is Completed its task
Success: Thread 5 is Processing now
Exit: Thread 5 is Completed its task
当然,也可以给WaitOne加一个期限,WaitOne(Int32),如果超过规定时间都没有获取到mutex,那么将返回false,且不会再尝试获取mutex。看下面的例子:
class Program
{
private static Mutex mutex = new Mutex();
static void Main(string[] args)
{
//Create multiple threads to understand Mutex
for (int i = 1; i <= 3; i++)
{
Thread threadObject = new Thread(MutexDemo)
{
Name = "Thread " + i
};
threadObject.Start();
}
Console.ReadKey();
}
//Method to implement syncronization using Mutex
static void MutexDemo()
{
// Wait until it is safe to enter, and do not enter if the request times out.
Console.WriteLine(Thread.CurrentThread.Name + " Wants to Enter Critical Section for processing");
if (mutex.WaitOne(1000))
{
try
{
Console.WriteLine("Success: " + Thread.CurrentThread.Name + " is Processing now");
Thread.Sleep(2000);
Console.WriteLine("Exit: " + Thread.CurrentThread.Name + " is Completed its task");
}
finally
{
//Call the ReleaseMutex method to unblock so that other threads
//that are trying to gain ownership of the mutex can enter
mutex.ReleaseMutex();
Console.WriteLine(Thread.CurrentThread.Name + " Has Released the mutex");
}
}
else
{
Console.WriteLine(Thread.CurrentThread.Name + " will not acquire the mutex");
}
}
~Program()
{
mutex.Dispose();
}
}
当然,为了控制实例程序同事访问,我们可以使用OpenExist函数来进行判断,其定义如下:
public static System.Threading.Mutex OpenExisting (string name);
该函数会试图获取指定名称的mutex对象,如果存在则返回该对象,如果不存在则抛出异常,因此我们可以在程序第一个实例时创建mutex对象,当后面启动新的实例后,判断已经存在,那么就阻止实例启动。例子如下:
internal class SingleInstance
{
static Mutex? _mutex;
public static void Run()
{
if(!IsSingleInstance())
{
Console.WriteLine("More than one instance");
Thread.Sleep(TimeSpan.FromSeconds(2));
return;
}
else
{
Console.WriteLine("One instance");
}
Console.ReadLine();
}
static bool IsSingleInstance()
{
try
{
Mutex.OpenExisting("MyApp");
}
catch
{
_mutex=new Mutex(true,"MyApp");
//Only one instance
return true;
}
//more than one Instance.
return false;
}
}
连续运行该程序两次,会发现第二次运行后,实例在2秒自动关闭。
虽然Mutex可以解决程序多启动问题,确保外部线程只有一个可以访问,但是如果要精确控制外部线程数量,Mutex就无能为力了,这就需要用到下一节讲的Semaphore类了。