5天不再惧怕多线程——第四天 信号量

    今天整理“信号量”的相关知识,其实想想也蛮有趣的,锁,互斥,信号量都可以实现线程同步,在framework里面主要有三种。

<1>:ManualResetEvent

<2>:AutoResetEvent

<3>: Semaphore

 

好,下面就具体看看这些玩意的使用。

 

一:ManualResetEvent

      该对象有两种信号量状态True和False,好奇的我们肯定想知道True和False有什么区别,稍后的例子见分晓,有三个方法值得学习一下。

1:WaitOne

     该方法用于阻塞线程,默认是无限期的阻塞,有时我们并不想这样,而是采取超时阻塞的方法,如果超时就放弃阻塞,这样也就避免了无限期

       等待的尴尬。

2:Set

     手动修改信号量为True,也就是恢复线程执行。

3:ReSet

     手动修改信号量为False,暂停线程执行。

 

好了,下面举个例子说明一下。

 

<1>  信号量初始为False,WaitOne采用无限期阻塞,可以发现线程间可以进行交互。

public class Example
{
    public static void Main()
    {
        Thread t = new Thread(Run);

        t.Name = "Jack";

        Console.WriteLine("当前时间:{0}  {1} {1},我是主线程,收到请回答。", DateTime.Now, t.Name);

        t.Start();

        Thread.Sleep(5000);

        mr.Set();

        Console.Read();
    }

    static ManualResetEvent mr = new ManualResetEvent(false);

    static void Run()
    {
        mr.WaitOne();

        Console.WriteLine("\n当前时间:{0}  主线程,主线程,{1}已收到!", DateTime.Now, Thread.CurrentThread.Name);
    }
}

 

<2> 信号量初始为True,WaitOne采用无限期阻塞,实验发现WaitOne其实并没有被阻塞。

 static ManualResetEvent mr = new ManualResetEvent(true);

 

<3>信号量初始为False,WaitOne采用超时2s,虽然主线程要等5s才能进行Set操作,但是WaitOne已经等不及提前执行了。

public class Example
{
    public static void Main()
    {
        Thread t = new Thread(Run);

        t.Name = "Jack";

        Console.WriteLine("当前时间:{0}  {1} {1},我是主线程,收到请回答。", DateTime.Now, t.Name);

        t.Start();

        Thread.Sleep(5000);

        mr.Set();

        Console.Read();
    }

    static ManualResetEvent mr = new ManualResetEvent(false);

    static void Run()
    {
        mr.WaitOne(2000);

        Console.WriteLine("\n当前时间:{0}  主线程,主线程,{1}已收到!", DateTime.Now, Thread.CurrentThread.Name);
    }
}

二:AutoResetEvent

      在VS对象浏览器中,我们发现AutoResetEvent和ManualResetEvent都是继承于EventWaitHandle,所以基本功能是一样的,不过值得注意

的一个区别是WaitOne会改变信号量的值,比如说初始信号量为True,如果WaitOne超时信号量将自动变为False,而ManualResetEvent则不会。

public class Example
{
    public static void Main()
    {
        Thread t = new Thread(Run);

        t.Name = "Jack";

        t.Start();

        Console.Read();
    }

    static AutoResetEvent ar = new AutoResetEvent(true);

    static void Run()
    {
        var state = ar.WaitOne(1000, true);

        Console.WriteLine("我当前的信号量状态:{0}", state);

        state = ar.WaitOne(1000, true);

        Console.WriteLine("我恨你,不理我,您现在的状态是:{0}", state);

    }
}

 

三:Semaphore 

     这玩意是.net 4.0新增的,用于控制线程的访问数量,默认的构造函数为initialCount和maximumCount,表示默认设置的信号量个数和

最大信号量个数,其实说到底,里面是采用计数器来来分配信号量,当你WaitOne的时候,信号量自减,当Release的时候,信号量自增,然而

当信号量为0的时候,后续的线程就不能拿到WaitOne了,所以必须等待先前的线程通过Release来释放。

 

好了,下面还是举例子来说明一下:

 

<1> initialCount=1,maximunCount=10,WaitOne采用无限期等待。

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {

            Thread t1 = new Thread(Run1);
            t1.Start();

            Thread t2 = new Thread(Run2);
            t2.Start();

            Console.Read();
        }

        static Semaphore sem = new Semaphore(1, 10);

        static void Run1()
        {
            sem.WaitOne();

            Console.WriteLine("大家好,我是Run1");
        }

        static void Run2()
        {
            sem.WaitOne();

            Console.WriteLine("大家好,我是Run2");
        }
    }
}

我们悲剧的发现t2线程不能执行,我们知道WaitOne相当于自减信号量,然而默认的信号量个数为1,所以t2想执行必须等待t1通过Release来释放。

static void Run1()
        {
            sem.WaitOne();

            Console.WriteLine("大家好,我是Run1");

            sem.Release();
        }

 

可能有的同学要问,我不是设置了maximunCount=10吗?为什么没有起到作用?是的,默认情况下是没有起到作用,必须要我们手动干预一下,

我们知道调用Release方法相当于自增一个信号量,然而Release有一个重载,可以指定自增到maximunCount个信号量,这里我就在主线程上

Release(10),看看效果。

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {

            Thread t1 = new Thread(Run1);
            t1.Start();

            Thread t2 = new Thread(Run2);
            t2.Start();

            Thread.Sleep(1000);

            sem.Release(10);

            Console.Read();
        }

        static Semaphore sem = new Semaphore(1, 10);

        static void Run1()
        {
            sem.WaitOne();

            Console.WriteLine("大家好,我是Run1");
        }

        static void Run2()
        {
            sem.WaitOne();

            Console.WriteLine("大家好,我是Run2");
        }
    }
}

<2> Semaphore命名,升级进程交互。

      在VS对象浏览器中发现Semaphore是继承字WaitHandle,而WaitHandle封装了win32的一些同步机制,所以当我们给Semaphore命名的时候

就会在系统中可见,下面举个例子,把下面的代码copy一份,运行两个程序。

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {

            Thread t1 = new Thread(Run1);
            t1.Start();

            Thread t2 = new Thread(Run2);
            t2.Start();

            Console.Read();
        }

        static Semaphore sem = new Semaphore(3, 10, "cnblogs");

        static void Run1()
        {
            sem.WaitOne();

            Console.WriteLine("当前时间:{0} 大家好,我是Run1", DateTime.Now);
        }

        static void Run2()
        {
            sem.WaitOne();

            Console.WriteLine("当前时间:{0} 大家好,我是Run2", DateTime.Now);
        }
    }
}

 

是的,我设置了信号量是3个,所以只能有三个线程持有WaitOne,后续的线程只能苦苦的等待。

时间: 2024-11-03 05:53:44

5天不再惧怕多线程——第四天 信号量的相关文章

5天不再惧怕多线程——第一天 尝试Thread

     原本准备在mongodb之后写一个lucene.net系列,不过这几天用到多线程时才发现自己对多线程的了解少之又少,仅仅停留在lock上面, 故这几天看了下线程参考手册结合自己的心得整理一下放在博客上作为自己的学习笔记.      好了,我们知道"负载"是一个很时尚,很牛X的玩意,往大处说,网站需要负载,数据库需要负载.往小处说,线程也需要负载,面对海量的 用户请求,我们的单线程肯定扛不住,那么怎么办,一定要负载,所以说多线程是我们码农必须要熟练掌握的一门技术.     在f

5天不再惧怕多线程——第五天 线程池

说到多线程,不可不说线程池,C#中关于池的概念很多,今天来整理下ThreadPool的使用. 是的,如果你很懒,如果你的执行任务比较短,如果你不想对线程做更精细的控制,那么把这些繁琐的东西丢给线程池吧. 一:ThreadPool 好了,下面看看TheadPool下有哪些常用的方法. 1:GetMaxThreads,GetMinThreads 首先我们肯定好奇线程池到底给我们如何控制线程数,下面就具体的看一看. class Program { static void Main(string[] a

5天不再惧怕多线程——第二天 锁机制

     当多个线程在并发的时候,难免会碰到相互冲突的事情,比如最经典的ATM机的问题,并发不可怕,可怕的是我们没有能力控制. 线程以我的理解可以分为三种 ① 锁. ② 互斥. ③ 信号.   好,这一篇主要整理"锁",C#提供了2种手工控制的锁 一:  Monitor类      这个算是实现锁机制的纯正类,在锁定的临界区中只允许让一个线程访问,其他线程排队等待.主要整理为2组方法.   1:Monitor.Enter和Monitor.Exit          微软很照护我们,给了

5天不再惧怕多线程——第三天 互斥体

     没想到我的前两篇文章还挺受欢迎的,谢谢大家,今天整理下Mutex的使用. 一:Mutex 首先看下MSDN对它的解释:      不错,出现了一个亮点,可用于"进程间同步",既然进程间都可以同步,那线程同步对它来说不是小菜一碟吗?好的,还是看下Mutex在 线程中发挥的神奇功效. 1: 线程间同步     Metux中提供了WatiOne和ReleaseMutex来确保只有一个线程来访问共享资源,是不是跟Monitor很类似,下面我还是举个简单的例子, 注意我并没有给Metu

李彦宏:让周鸿祎不再惧怕Google

    1999年,北京风入松书店.我翻到<硅谷商战>这本书时,看到作者是李彦宏,就放下了.我不认识李彦宏,不相信一个中国人能对硅谷有什么切身了解,以为这又是一本剪刀加浆糊攒出来的书.2000年冬,北京CD酒吧.我作为<计算机世界>报记者应邀请参加百度年终媒体答谢会.最后一个节目是"杀人"游戏.我第一次玩,轻易举手同意将李彦宏"误杀".李彦宏被"误杀"后告诉我,不应该先将他"杀掉",因为他是会玩的人,一

艾伟:C#多线程学习(四) 多线程的自动管理(线程池)

本系列文章导航 C#多线程学习(一) 多线程的相关概念 C#多线程学习(二) 如何操纵一个线程 C#多线程学习(三) 生产者和消费者 C#多线程学习(四) 多线程的自动管理(线程池) C#多线程学习(五) 多线程的自动管理(定时器) C#多线程学习(六) 互斥对象 在多线程的程序中,经常会出现两种情况: 一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPool(线程池)来解决: 另一种情况:线程平时都处于休眠状态,只是周期性地被

Android多线程的四种方式

当我们启动一个App的时候,Android系统会启动一个Linux Process,该Process包含一个Thread,称为UI Thread或Main Thread.通常一个应用的所有组件都运行在这一个Process中,当然,你可以通过修改四大组件在Manifest.xml中的代码块(<activity><service><provider><receiver>)中的android:process属性指定其运行在不同的process中.当一个组件在启动的

C#多线程学习(四)多线程的自动管理(线程池)

在多线程的程序中,经常会出现两种情况: 一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPool(线程池)来解决: 另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒 这一般使用Timer(定时器)来解决: ThreadPool类提供一个由系统维护的线程池(可以看作一个线程的容器),该容器需要 Windows 2000 以上系统支持,因为其中某些方法调用了只有高版本的Windows才有的API函数. 将线程安放在线程池里,需

让U盘不再自动运行的四种方案

  第一种: 这种方法比较简单,就是用户在放入U盘同时,按住Shift键直至U盘的指示灯熄灭,然后松开Shift键即可.这种方法是一种临时应急法,如果拥护之是偶尔项禁止自动播放功能,这种方法比较合适. 第二种 : 如果用户项彻底禁止U盘自动播放或在一段时间内禁止自动播放功能,可以试试这种方法:用鼠标右击"我的电脑",选择"属性",打开"设备管理器"选项卡,然后单击设备前的"+",选中光驱设备图标单击"属性"