再说多线程(三)——Mutex类

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 类的实例。

  1. Mutex():它使用默认属性初始化 Mutex 类的新实例。

  1. Mutex(bool initiallyOwned):它使用布尔值初始化 Mutex 类的新实例,该值指示调用线程是否应具有互斥锁的初始所有权。

  1. Mutex(bool initiallyOwned, string name):它用一个布尔值初始化 System.Threading.Mutex 类的一个新实例,该值指示调用线程是否应具有互斥锁的初始所有权,以及一个作为互斥锁名称的字符串.

  1. Mutex(bool initiallyOwned, string name, out bool createdNew):它使用布尔值初始化 System.Threading.Mutex 类的新实例,该布尔值指示调用线程是否应具有互斥锁的初始所有权,一个字符串即名称互斥锁的属性,以及一个布尔值,当方法返回时,该值指示调用线程是否被授予互斥锁的初始所有权。

  1. Mutex(bool initiallyOwned, string name, out bool createdNew, MutexSecurity mutexSecurity):它用一个布尔值初始化 System.Threading.Mutex 类的一个新实例,该值指示调用线程是否应该拥有互斥锁的初始所有权,一个字符串是互斥锁的名称,以及一个布尔变量,当方法返回时,它指示调用线程是否被授予互斥锁的初始所有权,以及要应用于指定互斥锁的访问控制安全性。

以下是 C# 中 Mutex 类的构造函数中使用的参数。

  1. initiallyOwned:如果作为调用的结果创建了命名系统互斥锁,则为调用线程提供命名系统互斥锁的初始所有权;否则,假的。

  1. name:互斥量的名称。如果该值为 null,则 Mutex 未命名。

  1. createdNew:当此方法返回时,包含一个布尔值,如果创建了本地互斥锁(即,如果名称为 null 或空字符串)或创建了指定的命名系统互斥锁,则该布尔值为真;如果指定的命名系统互斥量已经存在,则为 false。此参数在未初始化的情况下传递。

  1. mutexSecurity:一个 System.Security.AccessControl.MutexSecurity 对象,表示要应用于指定系统互斥锁的访问控制安全性。

2.2 Mutex成员函数

C# 中的 Mutex 类提供了以下方法。

  1. OpenExisting(string name):此方法用于打开指定的命名互斥量(如果它已存在)。它返回一个代表命名系统互斥锁的对象。此处,参数名称指定要打开的系统互斥锁的名称。如果名称为空字符串,它将抛出 ArgumentException。- 或 - 名称超过 260 个字符。如果名称为空,它将抛出 ArgumentNullException。

  1. OpenExisting(string name, MutexRights rights):此方法用于打开指定的命名互斥锁(如果它已经存在)并具有所需的安全访问权限。它返回一个代表命名系统互斥锁的对象。此处,参数名称指定要打开的系统互斥锁的名称。参数权限指定代表所需安全访问的枚举值的按位组合。

  1. TryOpenExisting(string name, out Mutex result):该方法用于打开指定的命名互斥量,如果它已经存在,并返回一个值,指示操作是否成功。此处,参数名称指定要打开的系统互斥锁的名称。当此方法返回时,结果包含一个 Mutex 对象,如果调用成功,该对象表示指定的互斥锁,如果调用失败,则返回 null。此参数被视为未初始化。如果命名的互斥体被成功打开,它返回真;否则,假的。

  1. TryOpenExisting(string name, MutexRights rights, out Mutex result):此方法用于打开指定的命名互斥锁(如果它已经存在),并具有所需的安全访问权限,并返回一个指示操作是否成功的值。此处,参数名称指定要打开的系统互斥锁的名称。参数权限指定代表所需安全访问的枚举值的按位组合。当此方法返回时,结果包含一个 Mutex 对象,如果调用成功,该对象表示指定的互斥锁,如果调用失败,则返回 null。此参数被视为未初始化。如果命名的互斥体被成功打开,它返回真;否则,假的。

  1. ReleaseMutex():该方法用于释放一次Mutex。

  1. GetAccessControl(): 此方法获取一个 System.Security.AccessControl.MutexSecurity 对象,该对象表示指定互斥体的访问控制安全性。它返回一个 System.Security.AccessControl.MutexSecurity 对象,表示指定互斥锁的访问控制安全性。

  1. 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类了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值