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

Mutex是C#中用于进程外线程线程安全的同步机制,防止多个外部进程同时访问共享资源。通过Mutex.WaitOne()方法,线程可以获取互斥锁,不允许其他线程同时进入临界区。文章介绍了Mutex的构造函数、OpenExisting方法以及如何通过Mutex实现单实例应用程序,展示了Mutex在限制外部线程访问数量上的作用。
摘要由CSDN通过智能技术生成

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

您查询的关键词是:delphi 同步 数据 。如果打开速度慢,可以尝试快速版;如果想保存快照,可以添加到搜藏。 (百度和网页http://blog.csdn.net/mygodsos/archive/2008/10/19/3097921.aspx的作者无关,不对其内容负责。百度快照谨为网络故障时之索引,不代表被搜索网站的即时页面。) -------------------------------------------------------------------------------- 发呆茶馆 登录 注册 欢迎 退出 我的博客 配置 写文章 文章管理 博客首页 全站 当前博客 空间 博客 好友 相册 留言 用户操作 [发私信] [加为好友] mygodsos 订阅我的博客 mygodsos的公告 文章分 Delphi Delphi学习--多线程 Delphi学习--自创的常用函数 期货大事记 生活感悟 投资理财 编程学习 万一的Delphi博客 存档 2009年05月(3) 2008年11月(13) 2008年10月(8) 2008年09月(3) ◆Delphi多线程编程之 同步读写全局数据 ◆(乌龙哈里2008-10-12) 收藏 ◆Delphi多线程编程之同步读写全局数据 ◆(乌龙哈里2008-10-12) (调试环境:Delphi2007+WinXPsp3 例程:Tst_Thread3.dpr) 开始研究最重要的多线程读写全局数据了,结合书上的例子,我修改成下面的情况: unit Tst_Thread3U; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; Button2: TButton; Button3: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private procedure ThreadsDone(Sender: TObject); end; TMyThread=class(TThread) protected procedure Execute;override; end; var Form1: TForm1; implementation {$R *.dfm} const MaxSize=128; var NextNumber:Integer=0; DoneFlags:Integer=0; GlobalArry:array[1..MaxSize] of Integer; Lock:byte; //1-不同步 2-临界区 3-互斥 CS:TRTLCriticalSection; //临界区 hMutex:THandle; //互斥 function GetNextNumber:Integer; begin Result:=NextNumber; inc(NextNumber); end; procedure TMyThread.Execute; var i:Integer; begin FreeOnTerminate:=True; //终止后自动free OnTerminate:=Form1.ThreadsDone; if Lock3 then //非互斥情况 begin if Lock=2 then EnterCriticalSection(CS); //建立临界区 for i := 1 to MaxSize do begin GlobalArry[i]:=GetNextNumber; Sleep(5); end; if Lock=2 then LeaveCriticalSection(CS);//离开临界区 end else //-------互斥 begin if WaitForSingleObject(hMutex,INFINITE)=WAIT_OBJECT_0 then begin for i := 1 to MaxSize do begin GlobalArry[i]:=GetNextNumber; Sleep(5); end; end; ReleaseMutex(hMutex); //释放 end; end; procedure TForm1.ThreadsDone(Sender: TObject); var i:Integer; begin Inc(DoneFlags); if DoneFlags=2 then begin for i := 1 to MaxSize do Memo1.Lines.Add(inttostr(GlobalArry[i])); if Lock=2 then DeleteCriticalSection(CS); //删除临界区 If Lock=3 then CloseHandle(hMutex); //关闭互斥 end; end; //非同步 procedure TForm1.Button1Click(Sender: TObject); begin Lock:=1; TMyThread.Create(False); TMyThread.Create(False); end; //临界区 procedure TForm1.Button2Click(Sender: TObject); begin Lock:=2; InitializeCriticalSection(CS); //初始化临界区 TMyThread.Create(False); TMyThread.Create(False); end; //互斥 procedure TForm1.Button3Click(Sender: TObject); begin Lock:=3; // 互斥 hMutex:=CreateMutex(0,False,nil); TMyThread.Create(False); TMyThread.Create(False); end; end. 没有临界区和互斥的帮助,两个线程都不断地在Memo1输出,而且数字是乱的。 一、临界区 所谓临界区,就是一次只能由一个线程来执行的一段代码。如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。 使用临界区的步骤: 1、先声明一个全局变量型为TRTLCriticalSection; 2、在线程Create()前调用InitializeCriticalSection()过程来初始化,该函数定义是: void WINAPI InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 型lpCriticalSection即是Delphi封装的TRTLCriticalSection。 3、在线程的需要放入临界区的代码前面使用EnterCriticalSection(lpCriticalSection)过程来开始建立临界区。在代码完成后用LeaveCriticalSection(lpCriticalSection)来标志临界区的结束。 4、在线程执行完后用DeleteCriticalSection(lpCriticalSection)来清除临界区。这个清除过程必须放在线程执行完后的地方,比如FormDesroy事件中。上面的例子中,若把该过程放在TMyThread.Create(False);后,会产生错误。 二、互斥: 互斥非常似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。 提示临界区与事件对象(比如互斥对象)的最大的区别是在性能上。临界区在没有线程冲突时,要用10~15个时间片,而事件对象由于涉及到系统内核要用400~600个时间片。 使用互斥的步骤: 1、声明一个型为Thandle或Hwnd的全局变量,其实都是Cardinal型。Hwnd是handle of window,主要用于窗口句柄;而Thandle则没有限制。 2、线程Create()前用CreateMutex()来创建一个互斥量。该函数定义为: HANDLE WINAPI CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName:Pchar); LPSECURITY_ATTRIBUTES参数为一个指向TSecurityAttributtes记录的指针。此参数设为nil,表示访问控制列表默认的安全属性。 bInitalOwner参数表示创建互斥对象的线程是否要成为此互斥对象的拥有者。当此参数为False时,表示互斥对象没有拥有者。 lpName参数指定互斥对象的名称。设为nil表示无命名,如果参数不是设为nil,函数会搜索是否有同名的互斥对象存在。如果有,函数就会返回同名互斥对象的句柄。否则,就新创建一个互斥对象并返回其句柄。 返回值是一handle。当错误发生时,返回null,此时用GetLastError函数可查看错误的信息。 利用CreateMutex()可以防止程序多个实例运行,如下例: Program ABC; Uses Forms,Windows,…; {$R *.res} Var hMutex:Hwnd; Begin Application.Initialize; hMutex:=CreateMutex(nil,False,Pchar(Application.Title)); if GetLastErrorERROR_ALREADY_EXISTS then begin //项目要运行的咚咚 end; ReleaseMutex(hMutex); Application.Run; End; 在本节的例程中,我们只是要防止线程进入同步代码区域中,所以lpName参数设置为nil。 3、在同步代码前用WaitForSingleObject()函数。该函数使得线程取得互斥对象(同步代码)的拥有权。该函数定义为: DWORD WINAPI WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds); 这个函数可以使当前线程在dwMilliseconds指定的时间内睡眠,直到hHandle参数指定的对象进入发信号状态为止。一个互斥对象不再被线程拥有时,它就进入发信号状态。当一个进程要终止时,它就进入发信号状态。dwMilliseconds参数可以设为0,这意味着只检查hHandle参数指定的对象是否处于发信号状态,而后立即返回。dwMilliseconds参数设为INFINITE,表示如果信号不出现将一直等下去。 这个函数的返回值含义: WAIT_ABANDONED 指定的对象是互斥对象,并且拥有这个互斥对象的线程在没有释放此对象之前就已终止。此时就称互斥对象被抛弃。这种情况下,这个互斥对象归当前线程所有,并把它设为非发信号状态 WAIT_OBJECT_0 指定的对象处于发信号状态 WAIT_TIMEOUT 等待的时间已过,对象仍然是非发信号状态 再次声明,当一个互斥对象不再被一个线程所拥有,它就处于发信号状态。此时首先调用WaitForSingleObject()函数的线程就成为该互斥对象的拥有者,此互斥对象设为不发信号状态。当线程调用ReleaseMutex()函数并传递一个互斥对象的句柄作为参数时,这种拥有关系就被解除,互斥对象重新进入发信号状态。 注意除WaitForSingleObject()函数外,你还可以使用WaitForMultipleObject()和MsgWaitForMultipleObject()函数,它们可以等待几个对象变为发信号状态。这两个函数的详细情况请看Win32 API联机文档。 4、在同步代码结束后,使用ReleaseMutex(THandle)函数来标志。该函数只是释放互斥对象和线程的拥有者关系,并不释放互斥对象的句柄。 5、调用CloseHandle(THandle)来关闭互斥对象。请注意例程中该函数的使用位置。 、还有一种用信号量对象来管理线程同步的,它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。有点复杂,想不到在哪可以用,现在就不研究论了。 发表于 @ 2008年10月19日 00:47:00 | 评论( loading... ) | 编辑| 举报| 收藏 旧一篇:◆delphi多线程编程之二 ◆(乌龙哈里2008-10-12) | 新一篇:◆Delphi多线程编程之四 线程安全和VCL ◆(乌龙哈里2008-10-12)Csdn Blog version 3.1a Copyright © mygodsos
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值