代码改变世界

NET多线程探索-互斥锁,信号量,事件(小新和拆弹部队友情演出)

2012-03-22 17:14  海不是蓝  阅读(2419)  评论(6编辑  收藏  举报

mutex互斥锁-不准确的时钟


概念性的东西:
互斥锁是一个互斥的同步对象,一个时间只有一个线程可以获取它。
前一篇文章中的时钟程序我们这里用Mutex互斥锁来实现。

class Program
{
    static void Main(string[] args)
    {
        Clock C = new Clock();
        C.RunClock(1);
        Console.Read();
    }
}
public class Clock
{
    public Mutex Mtx = new Mutex();

    //开始运行时钟,输入运行分钟
    public void RunClock(Int32 Minute)
    {
        Thread T1 = new Thread((object Minute1) =>
        {
            Int32 m = Convert.ToInt32(Minute1) * 60 / 2;
            while (m > 0)
            {
                DI(true);
                m--;
            }
        });
        Thread T2 = new Thread((object Minute1) =>
        {
            Int32 m = Convert.ToInt32(Minute1) * 60 / 2;
            while (m > 0)
            {
                DA(true);
                m--;
            }
        });
        T1.Start(true);
        T2.Start(true);
    }

    public void DI(bool run)
    {
        Mtx.WaitOne();
        if (!run)
        {
            Mtx.ReleaseMutex();
            return;
        }
        else
        {
            Console.WriteLine("嘀");
            Thread.Sleep(1000);
            Mtx.ReleaseMutex();
        }
    }

    public void DA(bool run)
    {
        Mtx.WaitOne();
        if (!run)
        {
            Mtx.ReleaseMutex();
            return;
        }
        else
        {
            Console.WriteLine("嗒");
            Thread.Sleep(1000);
            Mtx.ReleaseMutex();
        }
    }
}

图片1
为什么会这样?貌似一切都是合理的!


这里我只发表下我的理解,还请个位园友自己认真分析下,毕竟我个人理解可能是错误的!
如果您发现我理解错了,请在评论指出,我好及时学习更早!

Monitor的时钟是使用了lock来锁定,lock最后也会被编译成为Monitor的Enter和Exit。
重点是Monitor在Pulse(obj)的时候已经就确定了另外一个等待obj被释放的线程拥有了执行权!
而Mutex在ReleaseMutex在释放锁定之后,当前线程和其他等待线程都执行WaitOne,导致了线程执行权的争夺!
随着程序的运行,线程之间的争夺激烈。

mutex互斥锁-杯具的拆弹手



下面用互斥锁实现个例子,一个炸弹,同时只能一个人拆弹,如果超过1个人就爆炸。

class Program
{
    static void Main(string[] args)
    {
        炸弹 b = new 炸弹();
        b.拆弹();
        b.拆弹();
        Console.Read();
    }
}
public class 炸弹
{
    private Int32 热度 = 0;
    public Mutex m = new Mutex();

    public void 拆弹()
    {
        Thread t = new Thread(炸弹内部);
        t.Start();
    }

    private void 炸弹内部()
    {
        m.WaitOne();
        热度++;
        Thread.Sleep(1000);
        if (热度>1)
        {
            Console.WriteLine("炸弹爆炸!拆弹手见马克思...");
            m.ReleaseMutex();
            return;
        }
        热度--;
        Console.WriteLine("炸弹安全拆除!拆弹手这个月奖金加倍...");
        m.ReleaseMutex();
    }
}

图片2


拆弹手很幸运,这个月奖金加倍了。如果把Mutex去掉,那么马克思就等着他们去聊天。

信号量-Semaphore


互斥锁和同步都是锁定一个资源,同时只让一个线程去操作。
对于可以允许限定数量线程执行的情况互斥锁就不适合了,这里就需要信号量。
信号量通过一个计数器来控制对共享资源的访问,如果计数器的闲置数大于0,那么就允许访问,如果=0就拒绝访问。

public Semaphore(int initialCount, int maximumCount);

initialCount:可以同时授予的信号量的初始请求数。
maximumCount:可以同时授予的信号量的最大请求数。

public virtual bool WaitOne();
public int Release();

WaitOne():阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。
Release():退出信号量并返回前一个计数。

下面还是请出我们杯具的拆弹手来演示。

class Program
{
    static void Main(string[] args)
    {
        //有关部门的砖家叫兽告诉拆弹手炸弹最多4个人同时拆
        Int32 拆弹手人数 = 4;
        炸弹 b = new 炸弹(拆弹手人数);
        while (拆弹手人数 > 0)
        {
            b.拆弹();
            拆弹手人数--;
        }
        Console.Read();
    }
}
public class 炸弹
{
    private Int32 最高温度 = 0;
    private Int32 热度 = 0;
    private Semaphore S;

    public 炸弹(Int32 limit)
    {
        最高温度 = 3;
        S = new Semaphore(limit, limit);
    }

    public void 拆弹()
    {
        Thread t = new Thread(炸弹内部);
        t.Start();
    }

    private void 炸弹内部()
    {
        S.WaitOne();
        热度++;
        Thread.Sleep(1000);
        if (热度 > 最高温度)
        {
            Console.WriteLine("炸弹爆炸!拆弹手见马克思...");
            S.Release();
            return;
        }
        热度--;
        Console.WriteLine("炸弹安全拆除!拆弹手这个月奖金加倍...");
        S.Release();
    }
}

1

杯具的拆弹手,这里砖家叫兽错误指出最多4个人,而炸弹最高支持3个人!唉。。。

 
3

使用事件实现-幸福的小新


事件是另外一种同步对象,ManualResetEvent,AutoResetEvent2个类实现了事件同步的功能,都派生自EventWaitHandle类。

介绍下ManualResetEvent。

public ManualResetEvent(bool initialState);
public virtual bool WaitOne();
public bool Reset();
public bool Set();


第一个是构造函数,传入的变量是事件是否发出信号。
第二个是等待一个事件发出信号,期间是阻塞的。
第三个是重置事件为未发出信号状态。
最后一个是设置事件为发出信号状态。

ManualResetEvent和AutoResetEvent的主要实现都是相同,只有一点不同,就是ManualResetEvent需要手动设置事件为未发出信号,而AutoResetEvent是自动设置的。

野原新之助的一天

class Program
{
    static void Main(string[] args)
    {
        ManualResetEvent mal = new ManualResetEvent(false);
        美女姐姐 美女 = new 美女姐姐();
        小新 新之助 = new 小新();
        Thread t1 = new Thread(new ParameterizedThreadStart(美女.美女姐姐上街));
        Thread t2 = new Thread(new ParameterizedThreadStart(新之助.小新出动));
        t1.Start(mal);
        t2.Start(mal);
        Console.Read();
    }

    public class 美女姐姐
    {
        public void 美女姐姐上街(object Mal)
        {
            Thread.Sleep(2000);
            Console.WriteLine("青春美丽的美女姐姐上街了...");
            Thread.Sleep(1000);
            ((ManualResetEvent)Mal).Set();
        }
    }

    public class 小新
    {
        public 小新()
        {
            Thread.Sleep(1000);
            Console.WriteLine("小新拿着奶茶,猥琐的躲在大街边的绿化带里...");
            Thread.Sleep(1000);
        }

        public void 小新出动(object Mal)
        {
            ManualResetEvent Mal1 = (ManualResetEvent)Mal;
            Console.WriteLine("小新左右寻找美女姐姐...");
            //等待美女姐姐的出现
             Mal1.WaitOne();
            Console.WriteLine("小新跑到美女姐姐面前,脱下小裤裤,亮出大象,开始跳大象舞...");
            Console.WriteLine("屁股扭扭,“大象,大象,大象...”");
            Mal1.Reset();
        }
    }
}

2


不解释了。。。
4

 

总结下吧!


写了这么多,应该好好思考下这些同步方式,每种虽然大概思路是一样!但是绝对有各自的特点。
有空大家还是看看CLR 3版中的线程核心部分,里面把这些同步的原理讲的很清楚。
总之线程的水太深,慢慢游吧,下一篇把剩下的一些线程同步方式讲下,然后就在最后对NET线程同步做个总结。

作者:海不是蓝
博客:hailan2012
邮箱:hailan2012@sina.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。