zoukankan      html  css  js  c++  java
  • .Net CLR 中的同步机制(二): 信号量Semaphore

    信号量Semaphore是另外一个CLR中的内核同步对象。在.net中,类Semaphore封装了这个对象。与标准的排他锁对象(Monitor,Mutex,SpinLock)不同的是,它不是一个排他的锁对象,它与SemaphoreSlim,ReaderWriteLock等一样允许多个有限的线程同时访问共享内存资源。

    Semaphore就好像一个栅栏,有一定的容量,当里面的线程数量到达设置的最大值时候,就没有线程可以进去。然后,如果一个线程工作完成以后出来了,那下一个线程就可以进去了。Semaphore的WaitOne或Release等操作分别将自动地递减或者递增信号量的当前计数值。当线程试图对计数值已经为0的信号量执行WaitOne操作时,线程将阻塞直到计数值大于0。在构造Semaphore时,最少需要2个参数。信号量的初始容量和最大的容量。

            static Semaphore s1 = new Semaphore(1,2);
            
            static void Main(string[] args)
            {
                Task.Factory.StartNew(() => DoWork());
                Task.Factory.StartNew(() => DoWork());
     
                Console.ReadLine();
            }
     
            static void DoWork()
            {
                try
                {
                    Console.WriteLine(string.Format("Thread {0} try to do work.", Thread.CurrentThread.ManagedThreadId));
                    s1.WaitOne();
                    Console.WriteLine(string.Format("Thread {0} is doing work.", Thread.CurrentThread.ManagedThreadId));
                    Thread.Sleep(5000);
                    Console.WriteLine(string.Format("Thread {0} is finising work.", Thread.CurrentThread.ManagedThreadId));
                }
                finally
                {
                    s1.Release();
                }
            }

    上面例子中,线程池中第二个执行的线程会被阻塞5秒钟。因为构造函数中,初始容量为1,只能允许一个线程经过。

    最大容量为1的Semaphore,就类似于排他的锁对象,Monitor和Mutex。只是Semaphore是一种没有所有者的锁对象。我们在使用Mutex和Monitor的时候,只有该对象的所有者(成功获取该锁对象)才能释放锁对象,而Semaphore则不然,任何线程可以成功调用Semaphore的Release方法。

    通常我们可以需要在保护有限的资源上面使用信号量,比如说控制数据库连接池的连接数,限制共享内存访问的线程数等等。需要注意的是,当最大容量大于1的时候,我们并不能用来保证数据同步性,因为有多个线程可以同时进入临界区。而容量为1的则可以保证。

    下面是一个阻塞有界队列的数据结构,可以用在生产者和消费者模式中。

        public class BlockingBoundedQueue<T>
        {
            private Queue<T> _queue = new Queue<T>();
            private Mutex _mutex = new Mutex();
            private Semaphore _producerSemaphore;
            private Semaphore _consumerSemaphore;
     
            public BlockingBoundedQueue(int maxProducerCount, int maxConsumerCount)
            {
                _producerSemaphore = new Semaphore(maxProducerCount, maxProducerCount);
                _consumerSemaphore = new Semaphore(0, maxConsumerCount);
            }
     
            public void Enqueue(T obj)
            {
                //如果生产者到上限或队列满了,则阻塞,等待消费者取走一个可用项。
                _producerSemaphore.WaitOne();
     
                _mutex.WaitOne();
     
                try
                {
                    _queue.Enqueue(obj);
                }
                finally 
                {
                    _mutex.ReleaseMutex();
                }
                //有一个可用项,唤醒一个消费者。
                _consumerSemaphore.Release();
            }
     
            public T Dequeue()
            {
                //如果消费者到上限或队列为空,则阻塞,等待消费者取走一个可用项。
                _consumerSemaphore.WaitOne();
     
                T obj = default(T);
     
                _mutex.WaitOne();
     
                try
                {
                    obj = _queue.Dequeue();
                }
                finally
                {
                    _mutex.ReleaseMutex();
                }
                //取走一个可用项,唤醒一个生产者。
                _producerSemaphore.Release();
     
                return obj;
            }
        }

    该示例使用了Semaphore和上一篇说到的Mutex,我们用Mutex来实现数据同步,使用Semaphore来实现控制同步。由于都使用了内核对象,所以该数据结构的效率并不高。

    和Mutex类似,Semaphore可以用来控制进程级别的同步,由于Semaphore的特点,我们可以控制0-N个进程同时运行。

            static void TestProcessCount()
            {
                bool reatedNew = false;
                //控制运行2个进程
                using (var s = new Semaphore(2, 2, "Global\\Demo", out reatedNew))
                {
                    if (!s.WaitOne(TimeSpan.FromSeconds(5), true))
                    {
                        Console.WriteLine("Another app instance is running. Bye!");
                        return;
                    }
                    Console.WriteLine("Runing...");
                    Console.ReadLine();
                    s.Release();
                }
            }

    Semaphore的WaitOne或者Release方法的调用大约会耗费1微秒的系统时间,而优化后的SemaphoreSlim则需要大致四分之一微秒。在计算中大量频繁使用它的时候SemaphoreSlim还是优势明显,加上SemaphoreSlim还丰富了不少接口,更加方便我们进行控制,所以在4.0以后的多线程开发中,推荐使用SemaphoreSlim。

    测试代码从这里下载

  • 相关阅读:
    使用钉钉对接禅道的bug系统,实现禅道提的bug实时在钉钉提醒并艾特对应的开发人员处理
    Python3数据驱动ddt
    Python3发送邮件功能
    Python3的日志添加功能
    【最小生成树】BZOJ1016: [JSOI2008]最小生成树计数
    【k短路&A*算法】BZOJ1975: [Sdoi2010]魔法猪学院
    【最小生成树+子集枚举】Uva1151 Buy or Build
    【最小生成树】UVA1494Qin Shi Huang's National Road System秦始皇修路
    【最小生成树+贪心】BZOJ1821: [JSOI2010]Group 部落划分 Group
    【差分+前缀和】BZOJ1637: [Usaco2007 Mar]Balanced Lineup
  • 原文地址:https://www.cnblogs.com/haoxinyue/p/2916362.html
Copyright © 2011-2022 走看看