基元线程同步——内核模式构造(WaitHandle,EventWaitHandle,AutoResetEvent,ManualResetEvent,Semaphore,Mutex)

2022-02-27 13:21:08 浏览数 (1)

一、内核模式构造

内核模式构造,采用的是windows操作系统来同步线程,比VolatileRead,VolatileWrite,Interlocked等用户模式的构造慢很多。相对于用户模式的构造,它也有自己的优点:

1,不用像用户模式那样占着cpu“自旋”,浪费cpu资源。

2,内核模式可同步在同一机器不同进程中运行的线程。

3,可实现本地和托管线程相互之间的同步。

4,一个线程可以一直阻塞,直到一个集合中的内核对象全部可用,或部分可用。(WaitAll,WaitAny)

5,阻塞一个线程时,可以指定一个超时值,超过这个时间就解除阻塞。

二、FCL提供的内核模式构造层次结构

WaitHandle(抽象类)

    |——EventWaitHandle

         |——AutoResetEvent

         |——ManualResetEvent

    |——Semaphore

    |——Mutex

他们都继承了WaitHandle抽象类,WaitHandle提供了下列共同的静态方法:

WaitOne:阻塞调用线程,直到收到一个信号。

WaitAny:阻塞调用线程,直到收到任意一个信号。

WaitAll:阻塞调用线程,直到收到全部信号。

SingleAndWait:向指定的内核对象发出信号,并等待另一个内核对象收到信号。

Close/Dispose:关闭内核对象句柄。

2.1 EventWaitHandle

它属于事件(event),事件是内核维护的Boolean变量。如果事件为false,在事件上等待的线程就阻塞;如果事件为true,就解除阻塞。它主要有两个方法:

Set:将事件设为true。

ReSet:将事件设为false。

注意:初始化的时候我们可以指定事件的初始值,比如下面的例子就是指定初始时事件为false。

代码语言:javascript复制
            EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);//等同于AutoResetEvent
            EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset);//等同于ManualResetEvent

只听到从架构师办公室传来架构君的声音: 纵岫壁千寻,榆钱万叠,难买春留。有谁来对上联或下联?

2.2 AutoResetEvent

AutoResetEvent是EventWaitHandle的一个简单包装,内部没有额外的任何逻辑。它最大的特点就是,调用了Set方法将事件设为true之后,其中一个等待线程得到执行后,它会自动调用Reset方法,将事件信号设为false,以阻塞其它的线程。相当于放一个线程进来,门自动就关了(自动门)。

例子,使用AutoResetEvent实现一个简单的线程同步锁。

代码语言:javascript复制
此代码由Java架构师必看网-架构君整理
        private class SimpleWaitLock : IDisposable
        {
            //初始化一定要是true,否者,第一个调用Enter方法的线程会被阻塞
            private AutoResetEvent are = new AutoResetEvent(true);

            #region IDisposable 

            public void Enter()
            {
                are.WaitOne();//第一个线程调用这个方法后,事件将会为false,其他线程会被阻塞
                Console.WriteLine("thread={0}", Thread.CurrentThread.ManagedThreadId);
            }
            public void Exit()
            {
                are.Set();//退出时,将事件信号设为true,放一个线程进来后,马上设为false(调用reset)
            }
            public void Dispose()
            {
                are.Dispose();
            }

            #endregion
        }

2.3 ManualResetEvent

ManualResetEvent是EventWaitHandle的一个简单包装,内部也没有额外的任何逻辑。它和AutoResetEvent唯一的不同是,调用了Set方法将事件设为true后,不会去调用Reset方法,这将导致事件一直处于true,其它等待的多个线程都会得到执行,直到你手动调用Reset方法。相当于你把门打开后,需要手动去关(非自动门)。

2.4 Semaphore

信号量(semaphore)是内核维护的一个Int32的变量。信号量为0时,在信号量上等待的线程会阻塞;信号量大于0时,就解除阻塞。主要方法:

Release():就是一个加1的操作

Release(int32 releasecount):就是一个加releasecount的操作。

初始化semaphore时,可以指定最大和最小信号量的值。

用Sempphore实现同样功能的同步锁:

代码语言:javascript复制
        private class SimpleWaitLock : IDisposable
        {
            //初始化指定计数值为1,允许第一个线程可用
            private Semaphore sp = new Semaphore(1, 1);

            #region IDisposable

            public void Enter()
            {
                sp.WaitOne();//第一个线程调用这个方法后,计数值减1,变为0,其他线程会被阻塞
                Console.WriteLine("thread={0}", Thread.CurrentThread.ManagedThreadId);
            }
            public void Exit()
            {
                sp.Release();//计数值加1,其他线程可用
            }
            public void Dispose()
            {
                sp.Dispose();
            }

            #endregion
        }

2.5 Mutex

互斥体(mutex)和计数值为1的Semaphore或AutoResetEvent的工作方式非常相似。这三种方式每次都只释放一个等待的线程。主要方法:

ReleaseMutex():计数减去1

它有一个最大的不同是,它可以在同一线程上循环调用,也就是多次调用WaitOne(),然后在调用等次数的ReleaseMutex()。直到Mutex的计数为0时,其他等待的线程才能被调用。这种方式在平常中可能不太会用到。

可以用Mutex来防止应用程序二次启动,这在平常工作中也经常会碰到。

简单示例:

代码语言:javascript复制
此代码由Java架构师必看网-架构君整理
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            bool createNew;
            Mutex mutex = new Mutex(false, "ApplicationGuidName", out createNew);
            //没有启动,就创建一个新的
            if (createNew)
            {
                Application.Run(new Form1());
            }
            else
            {
                // 已经启动了
                MessageBox.Show("程序已经启动,不能重复启动!");
            }
        }

  注意:也可以用Semaphore和EventWaithandle来实现上面的功能,原理是一样的。但AutoResetEvent和ManualResetEvnet不行,他们没有提供类似的构造方法。

2.6 在一个内核模式变得可用时调用一个方法

可以通过ThreadPool.RegisterWaitForSingleObject方法注册一个方法。当一个事件收到信号,或是指定的时间超时,就会自动调用这个方法。

这个方法对于AutoResetEvent特别有用。但不太适合ManualResetEvent,因为要手动去调用Reset方法,不然会无限的调用这个方法。

代码语言:javascript复制
        private void TestAutoCallBack()
        {
            AutoResetEvent mre = new AutoResetEvent(false);
            //设定超时为2000ms
            ThreadPool.RegisterWaitForSingleObject(mre, new WaitOrTimerCallback(WaitCallBack),
                "123", 2000, false);

            //发出一个信号
            mre.Set();

            //故意等代3000ms
            Thread.Sleep(3000);
        }

        //有信号或超时就会调用这个方法
        private void WaitCallBack(object state, bool timeOut)
        {
            Console.WriteLine("is time out = {0}", timeOut);
        }

运行结果:

代码语言:javascript复制
is time out = False //得到信号时的调用
is time out = True  //超时的调用
is time out = True  //超时的调用

0 人点赞