前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基元线程同步——内核模式构造(WaitHandle,EventWaitHandle,AutoResetEvent,ManualResetEvent,Semaphore,Mutex)

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

作者头像
Java架构师必看
发布2022-02-27 13:21:08
3710
发布2022-02-27 13:21:08
举报
文章被收录于专栏:Java架构师必看

一、内核模式构造

内核模式构造,采用的是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  //超时的调用
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-02-262,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、内核模式构造
  • 二、FCL提供的内核模式构造层次结构
    • 2.1 EventWaitHandle
      • 2.2 AutoResetEvent
        • 2.3 ManualResetEvent
          • 2.4 Semaphore
            • 2.5 Mutex
              • 2.6 在一个内核模式变得可用时调用一个方法
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档