Form1单例模式与互斥锁

一、使用mutex来解决。

如何让窗体Form1也是一个单例模式呢?

在窗体项目中找到Program.cs,双击。找到入口点,更改如下:

cs 复制代码
    [STAThread]
    private static void Main()
    {
        string mutexName = "MyapplicatonMutexApp1121";
        using (Mutex m = new Mutex(true, mutexName, out bool isFist))
        {
            if (isFist == false)
            {
                MessageBox.Show("已经在运行了.");
                return;
            }
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }

上述代码是一个C#控制台应用程序的入口方法Main,它使用了STAThread属性和互斥锁来

控制应用程序的运行。

首先,定义了一个字符串变量mutexName来表示互斥锁的名称。互斥锁是一种同步机制,

用于在多个线程之间实现资源的互斥访问,确保同一时间只有一个线程可以访问被保护的资

源。

接下来,使用using语句以编程方式创建一个互斥锁Mutex。Mutex构造函数的第一个参数

为true,表示创建一个初始状态为"有信号"的互斥锁。第二个参数是互斥锁的名称,用于唯一

标识这个互斥锁。第三个参数out bool isFirst是一个out参数,用于返回一个布尔值,指示

是否是第一个线程获得了互斥锁。

接下来,通过检查isFirst的值来判断是否是第一个获得互斥锁的线程。如果isFirst的

值为false,则说明已经有另一个实例正在运行,弹出一个消息框提示用户已经在运行,并

直接退出应用程序;如果isFirst的值为true,则说明当前是第一个获得互斥锁的线程,继

续执行应用程序。

当应用程序第一次运行时,它将获得互斥锁,然后调用Application.EnableVisualStyles()和

Application.SetCompatibleTextRenderingDefault(false)方法来启用可视化样式和设置文

本呈现默认值。最后,调用Application.Run(new Form1())运行主窗体Form1,启动应用程序

的消息循环,处理用户界面和事件。

因此,上面保证只有一个实例运行,通过互斥锁来控制应用程序的运行。当多个实例尝

试运行时,只有第一个实例能够成功获得互斥锁并继续执行,其他实例将被提示已经在运行,

并退出应用程序。这种方法通常用于单实例应用程序,以确保不会同时启动多个相同的应用

程序实例。

问:上面[STAThread]有什么用?

答:[STAThread]是一个属性,用于指定应用程序的线程模型。上面应用于Main方法,用于确

保应用程序在单线程单元(STA)模式下执行。

在多线程应用程序中,STA模式是一种线程模型,它要求应用程序的所有线程都在单个单

元中执行,并且每个线程都与消息循环相关联。这对于许多GUI框架和组件是必需的,因为它

们通常依赖于在单个线程中进行消息处理。

[STAThread]属性告诉应用程序使用STA模式运行。该属性可以应用于整个应用程序(在

入口点Main方法上)或特定线程。确保应用程序以STA模式运行对于使用Windows Forms、WPF

或COM组件的应用程序是必要的。

如果应用程序需要与其他线程进行互操作,可能需要考虑使用不同的线程模型,如多线

程单元(MTA)模式。但在大多数情况下,使用[STAThread]来运行应用程序是常见和推荐的做

法。

STA(单线程单元)模式可以实现按顺序执行,减少多线程竞争资源的问题,并且能够

提供一致和可预测的程序执行顺序。但在某些情况下,例如需要处理大量计算密集型任务或

需要与其他线程进行并行操作的场景,MTA(多线程单元)模式可能更适合。在这种情况下,

需要仔细考虑线程同步和资源竞争问题,并采取适当的并发控制措施。

问:Mutex m = new Mutex(true, mutexName, out bool isFist)是什么意思?

答:此句用Mutex 类来创建一个互斥锁,并指定了参数。m 是一个 Mutex 类型的变量,用于

表示创建的互斥锁。

new Mutex(true, mutexName, out bool isFist) 是创建 Mutex 实例的构造函数调用:

true 表示在创建互斥锁时设置一个初始状态,即锁定状态。(上锁)

mutexName 是一个字符串参数,用于指定互斥锁的名称。

out bool isFist 是一个输出参数,用于指示是否是第一次创建互斥锁。

因此,这段代码的功能是创建一个名为 mutexName 的互斥锁,并在创建时将其锁定。

同时,通过 out bool isFist 参数返回一个布尔值,指示是否是第一次创建该互斥锁。

第一个参数 true 表示在创建互斥锁时将其初始化为锁定状态。这意味着在调用

new Mutex(true, mutexName, out bool isFist) 时,如果其他线程已经锁定了该互斥

锁,则创建过程将会失败,isFist 参数将会被设置为 false。

如果创建成功,则 isFist 参数将会被设置为 true,表示当前线程是第一个锁定

该互斥锁的线程。

如果第一个参数为 false,则无论其他线程是否已经锁定了该互斥锁,isFist 参数

都会被设置为 false。

通俗解释:mutex的Name属性很重要,它是识别mutex锁的重要标志。可以命名不同的锁,尽量长而具有唯一识别性,第二次来判断这个mutex锁就是根据这个Name。当然你也可以用唯一GUID来识别,但第二次运行时会重新生成另一个GUID,肯定与第一次的不同,除非,你把GUID保存在硬盘日志上,下次运行时,先读取硬盘从而取出GUID。为了简省,采取较长且多样字符的来命名Name,省去读日志文件的麻烦。

**m = new Mutex(true, mutexName, out bool isFist)**这一句就是上锁,设置初始拥有true,因此,如果上锁成功isFirst为true,说明之前没有实例进行上锁,没有人拥有这个锁。这个时候就可以运行实例。但如果现在上锁失败,说明已经被别人拥有,别人已经锁上了,自己进不了,也就是已经有实例在运行了,所以第二次运行只有退出,从而保证只有一实例。

二、理解Mutex

1、当我们想要确保多个线程同时访问某个共享资源时不会产生冲突时,可以使用Mutex类。

你可以把Mutex类看作是一个特殊的锁,它可以帮助我们控制对共享资源的访问。

你可以将Mutex类比作一间只能容纳一人的厕所。每当一个人想要使用厕所时,他必

须先查看门口的指示灯。如果指示灯是红色 的,意味着有人正在使用厕所,那么他就需

要等待。如果指示灯是绿色 的,意味着厕所是空闲的,他就可以进去并将指示灯设置为

红色,这样其他人就必须等待。

在代码中,我们创建了一个Mutex对象来代表这个厕所。当一个线程想要访问共享资

源时,它需要调用Mutex对象的WaitOne()方法来查看指示灯状态。如果指示灯是红色的

(即Mutex被锁定),线程就会被阻塞等待。当指示灯是绿色的时候,线程可以继续执行,

并调用Mutex对象的ReleaseMutex()方法将指示灯设置为红色,这样其他线程就必须等待。

使用Mutex类可以确保共享资源在同一时间只能由一个线程访问,从而避免了数据竞争和

冲突。它是一种有力的工具,用于实现多线程代码的同步和互斥操作。

2、Mutex类

Mutex类提供了对互斥锁进行操作和管理。常用的属性与方法:

Name属性:获取或设置Mutex对象的名称。

cs 复制代码
        Mutex myMutex = new Mutex(false, "MyMutex");// 创建一个具有指定名称的Mutex对象
        string mutexName = myMutex.Name;// 获取Mutex对象的名称

SafeWaitHandle属性:获取一个安全句柄,用于操作底层操作系统的同步原语。

cs 复制代码
        Mutex myMutex = new Mutex();// 创建一个Mutex对象
        SafeWaitHandle safeHandle = myMutex.SafeWaitHandle;// 获取SafeWaitHandle属性

**Mutex()**构造函数:创建一个Mutex对象。

cs 复制代码
        Mutex myMutex = new Mutex();// 创建一个Mutex对象,默认为非继承的互斥对象
        Mutex myInheritedMutex = new Mutex(true, "MyMutex", out bool createdNew);// 创建一个继承的互斥对象

**WaitOne()**方法:阻塞当前线程,直到Mutex对象变为可用。

cs 复制代码
        Mutex myMutex = new Mutex();// 创建一个Mutex对象
        myMutex.WaitOne();// 等待Mutex对象变为可用
        ...// 执行需要互斥访问的代码
        myMutex.ReleaseMutex();// 释放Mutex对象

**WaitOne(TimeSpan timeout)**方法:阻塞当前线程,直到Mutex对象变为可用,或者等待超时。

cs 复制代码
        Mutex myMutex = new Mutex();// 创建一个Mutex对象
        TimeSpan timeout = TimeSpan.FromSeconds(2);// 设置等待超时时间为2秒
        bool mutexAcquired = myMutex.WaitOne(timeout);// 等待Mutex对象变为可用,或者等待超时
        if (mutexAcquired)
        {
            ...// 执行需要互斥访问的代码
            myMutex.ReleaseMutex();// 释放Mutex对象
        }
        else
        {
            ...// 等待超时,执行其他操作
        }

**ReleaseMutex()**方法:释放Mutex对象,使其可供其他线程访问。

cs 复制代码
        Mutex myMutex = new Mutex();// 创建一个Mutex对象
        myMutex.WaitOne();// 等待Mutex对象变为可用
        ...// 执行需要互斥访问的代码
        myMutex.ReleaseMutex();// 释放Mutex对象

**Close()**方法:释放并关闭Mutex对象。

cs 复制代码
        Mutex mutex = new Mutex();// 创建一个Mutex对象
        ...// 执行一些操作
        mutex.Close();// 释放并关闭Mutex对象

问:Mutex只能在一个线程中吗?

答:不,Mutex在C#中并不限制只能在一个线程中使用。Mutex是一种系统级别的同步原

语,可以用于跨线程、跨进程的互斥操作。

在同一个进程中的多个线程可以使用同一个Mutex对象来实现互斥访问共享资源的目

的。当一个线程获取到Mutex的锁时,其他线程将会被阻塞等待,直到锁被释放。这样可

以确保同一个进程中的多个线程不会同时访问共享资源,避免出现冲突。

此外,Mutex还可以用于跨进程的互斥操作。在不同的进程中,可以通过使用具有相

同名称的Mutex对象来进行互斥操作。这样,不同进程中的线程就可以通过Mutex来同步

对共享资源的访问。

总结:Mutex既可以用于同一个线程内部的互斥操作,也可以用于不同线程、不同进

程之间的互斥操作,以实现并发控制和资源保护。

问:mutex中的Close与ReleaseMutex有什么区别?

答:Close()方法用于释放Mutex对象占用的资源。当你不再需要使用Mutex对象时,可以

调用Close()方法来显式地释放资源,以便后续的垃圾回收器可以回收相应资源。调用

Close()方法后,你将无法再使用该Mutex对象。

ReleaseMutex()方法用于释放Mutex对象的锁。当你在某个线程中调用WaitOne()方

法获取了Mutex的锁之后,你可以在适当的时候调用ReleaseMutex()方法来释放该锁,以

允许其他等待线程获得该锁并继续执行。每次调用WaitOne()方法成功后,需要对应调用

ReleaseMutex()方法来释放锁。

简言之:Close()方法用于释放对象;而ReleaseMutex()方法用于释放锁,对象仍在。

问:WaitOne()与WaitOne(TimeSpan)的区别?

答:两者都用于等待获取Mutex互斥锁。

WaitOne():没有参数,它会一直等待直到获取到Mutex或超时。如果Mutex当前不可

用,调用WaitOne()的线程将被阻塞,直到Mutex可用或者被中断。如果获取到Mutex,

WaitOne()会返回true。如果等待过程中发生了异常或调用被中断,WaitOne()会返回

false。

WaitOne(TimeSpan):接受一个TimeSpan类型的参数,用于指定最大等待时间。如果

Mutex在指定的时间内可用,该方法会获取到Mutex并返回true。如果Mutex在指定的时间

内不可用,该方法会返回false,表示超时。如果等待过程中发生了异常或调用被中断,

WaitOne(TimeSpan)会返回false。

WaitOne()没有超时设置,会一直阻塞等待;而WaitOne(TimeSpan)允许设置最大等

待时间,在超过该时间后会返回超时结果。WaitOne(0)立即返回结果。

3、上面的Mutex在取互拆锁时,可能超时,怎么修正单例模式?

cs 复制代码
        [STAThread]
        private static void Main()
        {
            string mutexName = "MyapplicatonMutexApp1121";
            using (Mutex m = new Mutex(true, mutexName, out bool isFist))
            {
                if (isFist == false)
                {
                    MessageBox.Show("已经在运行了.");
                    return;
                }
                bool acquiredLock = m.WaitOne(TimeSpan.FromSeconds(1));//等待1秒后尝试获取互斥锁
                if (!acquiredLock)//失败
                {
                    MessageBox.Show("获取互斥锁超时。");
                    return;//超时也认为在运行,所以需要人为再运行看提示。
                }

                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }

问:为什么会有延迟?

答:获取互斥锁时,可能会出现延迟的情况有多种原因,导致已经释放,但获取失败。

比如:线程调度、系统资源竞争、阻塞操作、垃圾回收等等,原因通常是在多线程

环境下或与外部资源的交互中比较常见的。虽然我们不能完全消除延迟,但可以采取一

些措施来最小化延迟的影响,例如使用合理的线程调度策略、避免过度竞争共享资源、

进行异步操作以减少阻塞等。

三、应用举例

一个常见的使用Mutex的经典例子是多个线程同时读写共享的全局变量。在这个例子中,

我们使用Mutex来确保同一时间只有一个线程能够访问共享变量。

cs 复制代码
        class Program
        {
            static int sharedVariable = 0;
            static Mutex mutex = new Mutex();

            static void Main(string[] args)
            {
                for (int i = 0; i < 5; i++)// 创建并启动多个线程
                {
                    Thread t = new Thread(IncrementSharedVariable);
                    t.Start();
                }
                
                Thread.Sleep(2000);// 等待所有线程执行完成
                Console.WriteLine("Shared Variable: " + sharedVariable);// 输出最终的共享变量的值

                Console.ReadLine();
            }

            static void IncrementSharedVariable()
            {
                mutex.WaitOne();// 等待获取Mutex的锁
                sharedVariable++;// 对共享变量进行操作
                mutex.ReleaseMutex();// 释放Mutex的锁
            }
        }

在上面的例子中,有多个线程同时执行IncrementSharedVariable方法来对

sharedVariable进行自增操作。由于使用了Mutex,每次只有一个线程能够获取到Mutex的锁,

执行自增操作并释放锁。

通过使用Mutex,我们确保了对sharedVariable的访问是互斥的,避免了多个线程同时

对其进行写操作导致的数据竞争和不确定行为。最终,我们可以正确地得到共享变量的最终

值。

上面因为执行很快,所以直接用了等待2秒。下面用程序来判断线程都结束.

cs 复制代码
        internal class Program
        {
            private static int shareVariable = 0;
            private static Mutex mutex = new Mutex();

            private static void Main(string[] args)
            {
                Thread[] threads = new Thread[5];
                for (int i = 0; i < 5; i++)
                {
                    threads[i] = new Thread(IncrementSharedVariable);
                    threads[i].Start();
                }
                foreach (Thread thread in threads)
                {
                    thread.Join();
                }
                Console.WriteLine(shareVariable);
                Console.ReadKey();
            }

            private static void IncrementSharedVariable()
            {
                mutex.WaitOne();
                shareVariable++;
                mutex.ReleaseMutex();
            }
        }

thread.Join()是什么意思?

当主线程开始运行时,会迅速创建并启动5个线程。然后,在主线程中使用foreach循环

遍历所有线程,并在每个线程上调用Join()方法,这会阻塞主线程,直到相应的线程执行完

成。

主线程会周期性地检查每个线程的状态,直到所有线程都执行完成。一旦所有线程执行

完成,Join()方法会立即返回,主线程不再被阻塞,接下来的代码会继续执行。

这种方式保证了主线程在输出最终的共享变量之前等待所有线程执行完成。它是一种有

效的方式来确保所有线程完成后再继续程序的执行。

简言之,主程序暂停,直到Join()里所有线程完成,主线程继续向下执行。

相关推荐
向宇it44 分钟前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
坐井观老天5 小时前
在C#中使用资源保存图像和文本和其他数据并在运行时加载
开发语言·c#
pchmi8 小时前
C# OpenCV机器视觉:模板匹配
opencv·c#·机器视觉
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭9 小时前
C#都可以找哪些工作?
开发语言·c#
boligongzhu11 小时前
Dalsa线阵CCD相机使用开发手册
c#
重生之我在字节当程序员12 小时前
如何实现单例模式?
单例模式
夕泠爱吃糖12 小时前
如何实现单例模式?
单例模式
m0_6075487612 小时前
什么是单例模式
开发语言·javascript·单例模式
Am心若依旧40912 小时前
[c++进阶(三)]单例模式及特殊类的设计
java·c++·单例模式