.NET 多线程同步

.NET 多线程同步包含SpinLock,Monitor(lock语法糖),WaitHandle族(ManualResetEvent、AutoResetEvent、Semaphore,Mutex等)

1. WaitHandle族(ManualResetEvent,AutoResetEvent,Semaphore等)

ManualResetEvent,AutoResetEvent是只允许一个线程访问共享资源,

ManualResetEvent,AutoResetEvent区别在于Set()

,ManualResetEvent实例调用Set()后所有线程允许运行不再阻塞,AutoResetEvent实例调用Set()后只允许运行一次且运行后立即阻塞

Semaphore 允许一个或多个

2.SpinLock

SpinLock即自旋锁,尝试获取该锁的线程持续不断的check是否可以获得。此时线程仍然是激活状态,只是在空转,浪费cpu而已

但是spinlock避免了线程调度和上下文切换,如果锁的时间极短的话,使用该锁反而效率会高,

SpinLock允许你查询是否锁已经被其他线程占用,通过IsHeld属性。

3.Mutex

Mutex是可以进程间同步的同步基元

 名称说明
System_CAPS_pubmethod Mutex()

使用默认属性初始化 Mutex 类的新实例。

System_CAPS_pubmethod Mutex(Boolean)

使用 Boolean 值(指示调用线程是否应具有互斥体的初始所有权)初始化 Mutex 类的新实例。

System_CAPS_pubmethod Mutex(Boolean, String)

使用 Boolean 值(指示调用线程是否应具有互斥体的初始所有权以及字符串是否为互斥体的名称)初始化 Mutex 类的新实例。

System_CAPS_pubmethod Mutex(Boolean, String, Boolean)

使用可指示调用线程是否应具有互斥体的初始所有权以及字符串是否为互斥体的名称的 Boolean 值和当线程返回时可指示调用线程是否已赋予互斥体的初始所有权的 Boolean 值初始化 Mutex 类的新实例。

System_CAPS_pubmethod Mutex(Boolean, String, Boolean, MutexSecurity)

使用可指示调用线程是否应具有互斥体的初始所有权以及字符串是否为互斥体的名称的 Boolean 值和当线程返回时可指示调用线程是否已赋予互斥体的初始所有权以及访问控制安全是否已应用到命名互斥体的 Boolean 变量初始化 Mutex 类的新实例。

Mutex并不适合于有相互消息通知的同步;另一方面而我们也多次提到局部Mutex应该被Monitor/lock所取代;而跨应用程序的、相互消息通知的同步由将在后面讲到的EventWaiteHandle/AutoResetEvent/ManualResetEvent承担更合适。所以,Mutex在.net中应用的场景似乎不多。不过,Mutex有个最常见的用途:用于控制一个应用程序只能有一个实例运行。

using System;
using System.Threading;

class MutexSample
{
    private static Mutex mutex = null;  //设为Static成员,是为了在整个程序生命周期内持有Mutex

    static void Main()
    {
        bool firstInstance;
       
        mutex = new Mutex(true, @"Global\MutexSampleApp", out firstInstance);
        try
        {
            if (!firstInstance)
            {
                Console.WriteLine ("已有实例运行,输入回车退出……");
                Console.ReadLine();
                return;
            }
            else
            {
                Console.WriteLine ("我们是第一个实例!");
                for (int i=60; i > 0; --i)
                {
                    Console.WriteLine (i);
                    Thread.Sleep(1000);
                }
            }
        }
        finally
        {
            //只有第一个实例获得控制权,因此只有在这种情况下才需要ReleaseMutex,否则会引发异常。
            if (firstInstance)
            {
                mutex.ReleaseMutex();
            }
            mutex.Close();
            mutex = null;
        }
    }
}

 

  这是一个控制台程序,你可以在编译后尝试一次运行多个程序,结果当然总是只有一个程序在倒数计时。你可能会在互联网上找到其它实现应用程序单例的方法,比如利用 Process 查找进程名、利用Win32 API findwindow 查找窗体的方式等等,不过这些方法都不能保证绝对的单例。因为多进程和多线程是一样的,由于CPU时间片随机分配的原因,可能出现多个进程同时检查到没有其它实例运行的状况。这点在CPU比较繁忙的情况下容易出现,现实的例子比如傲游浏览器。即便你设置了只允许一个实例运行,当系统比较忙的时候,只要你尝试多次打开浏览器,那就有可能“幸运”的打开若干独立的浏览器窗口。

  别忘了,要实现应用程序的单例,需要在在整个应用程序运行过程中都保持Mutex,而不只是在程序初始阶段。所以,例子中Mutex的建立和销毁代码包裹了整个Main()函数。

使用Mutex需要注意的两个细节

    1. 可能你已经注意到了,例子中在给Mutex命名的字符串里给出了一个“Global\”的前缀。这是因为在运行终端服务(或者远程桌面)的服务器上,已命名的全局 mutex 有两种可见性。如果名称以前缀“Global\”开头,则 mutex 在所有终端服务器会话中均为可见。如果名称以前缀“Local\”开头,则 mutex 仅在创建它的终端服务器会话中可见,在这种情况下,服务器上各个其他终端服务器会话中都可以拥有一个名称相同的独立 mutex。如果创建已命名 mutex 时不指定前缀,则它将采用前缀“Local\”。在终端服务器会话中,只是名称前缀不同的两个 mutex 是独立的 mutex,这两个 mutex 对于终端服务器会话中的所有进程均为可见。即:前缀名称“Global\”和“Local\”仅用来说明 mutex 名称相对于终端服务器会话(而并非相对于进程)的范围。最后需要注意“Global\”和“Local\”是大小写敏感的。
    2. 既然父类实现了IDisposalble接口,那么说明这个类一定需要你手工释放那些非托管的资源。所以必须使用try/finally,亦或我讨厌的using,调用Close()方法来释放Mutex所占用的所有资源

 

posted @ 2020-07-27 23:53  lldbang  阅读(258)  评论(0)    收藏  举报