zoukankan      html  css  js  c++  java
  • 基元线程同步——内核模式构造(WaitHandle,EventWaitHandle,AutoResetEvent,ManualResetEvent,Semaphore,Mutex)

    一、内核模式构造

    内核模式构造,采用的是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。

                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实现一个简单的线程同步锁。

            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实现同样功能的同步锁:

            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来防止应用程序二次启动,这在平常工作中也经常会碰到。

    简单示例:

            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方法,不然会无限的调用这个方法。

            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);
            }

    运行结果:

    is time out = False //得到信号时的调用
    is time out = True  //超时的调用
    is time out = True  //超时的调用
  • 相关阅读:
    Qt's Undo Framework
    linux-ftp
    Linux Terminator
    linux wc
    Linux卷配置管理
    vmware workstation LINUX磁盘扩容
    Oracle linux6.1配置yum本地源
    安装jdk java -version 不是自己所需要的版本
    aix下oracle数据库创建表空间和用户
    Linux命令 find和mv的结合使用:查找文件,移动到某个目录
  • 原文地址:https://www.cnblogs.com/xiashengwang/p/2662366.html
Copyright © 2011-2022 走看看