• 线程同步介绍及 生产者消费者问题举例 C#版


    现在有五个工人在果园摘水果,一次只能摘一个,摘下的水果放入一个框中,这种框最多只能装50个橘子,一共有两个这样的框。当一个工人装框时,其他工人不能在使用这个框。当两个框都装满了,工人只有等框里有剩余位置后,才能在摘果子。然后,有四个小孩来框里拿橘子,一次最多拿一个,并且当一个小孩在框前拿橘子时,其他小孩只能到另一个框拿橘子,如果两个框前都有小孩拿橘子,那么剩余要拿橘子的小孩只能等待。(这个题目是我自己编的,可能不是很准确)
    现在要设计一个程序模拟这样一个过程。
     
    分析:框是互斥资源,每次放橘子前 得判断有没有空闲得框,有就占住加锁,然后里面执行放橘子得方法。放完之后再解锁。框可以用队列表示。
    工人和小孩可以用Task模拟。
     
    这里需要两种锁,一种是放橘子得时候得一把Monitor锁,一种是当没有空闲得框后,加的AutoResetEvent锁。
    当使用两把锁得时候,需要特别小心,稍不注意都会引发死锁。
     
    Monitor锁再使用得时候,得用引用变量作为加锁得对象,不要用字符串和值变量。虽然再用值变量时,编译器不会报错,但是运行时,Enter会装箱,把值变量变为引用变量,但是再Exit时,依然是个值变量,这样Enter和Exit的锁变量就不是同一个变量,造成找不到锁的情况,就会抛出异常。
     
    另外使用Monitor枷锁时,应该使用 try{}finally{}语句块,保证总是会被解锁,否则遇到异常,不执行解锁语句,就死锁了。
    其实lock语句块就是Monitor的简便方法,内部使用的还是Monitor。
     
    对于AutoResetEvent而言,可以暂停和唤醒线程,再不同线程可以相互唤醒和阻塞。这样就非常的灵活。其实推荐使用ManualResetEvent,因为比起AutoResetEvent,可以唤起多个线程,如果说小孩一次拿多个橘子,这种方式就比AutoResetEvent有优势,因AutoResetEvent只唤醒一个线程。
     
    线程的同步还有其他方法,比如再数值上同步 有InterLock,其他的如信号量(Sema'phore)同步,CountDownEvent。
    同步的应用,还可以是用进程间同步的方法,实现在一台主机上,每次只能启动一个相同的应用程序,这时可以使用Mutex。
     
    避免资源在线程同步中互斥,还可以用 线程本地存储技术,ThreadLocal,例子:
    0
    详见:《C#本质论》第三版,第19章
    下面直接看代码:
     internal class Program
        {
            //最多容纳50个橘子每个框
           static  readonly int MAX = 50;
            //两个框
            static List<ConcurrentQueue<Orange>> Queues = 
                new List<ConcurrentQueue<Orange>>();
            //记录空闲的框
            static List<int> QidxBags = new List<int>();
    
            static int MaxO = 1000;     //最多摘1000个橘子
    
            static readonly object Sync = new object();
            static readonly object Sync2 = new object();
            //比起AutoResetEvent,可以唤起多个线程,如果说小孩一次拿多个橘子,而不是一个,
            //这种方式比AutoResetEvent有优势,因为AutoResetEvent只唤醒一个线程。
            static ManualResetEvent MResetEvent = new ManualResetEvent(false);
    
            static void Main(string[] args)
            {
                Queues.Add(new ConcurrentQueue<Orange>());
                Queues.Add(new ConcurrentQueue<Orange>());
               
                for (int i = 0; i < Queues.Count; i++)
                {
                    QidxBags.Add(i);
                }
                TaskProduceAndComsummer();
                Console.ReadKey();
            }
    
    
            static int GetQueuesIdx()
            {
                int idx = -1;
    
                int count = QidxBags.Count;
    
                if (count > 0)
                {
                    return count;
                }
    
    
                return idx;
            }
    
            static bool IsEmpty()
            {
                foreach (var item in Queues)
                {
                    if (item.Count >0)
                    {
                        return false;
                    }
                }
                return true;
            }
    
            static bool IsFull()
            {
                foreach (var item in Queues)
                {
                    if (item.Count < MAX)
                    {
                        return false;
                    }
                }
                return true;
            }
    
            static void TaskProduceAndComsummer()
            {
                for (int i = 0; i < 5; i++)
                {
                    string name = "工人_" + (i + 1);
                    Task t = new Task(Produce, (object)(name));
                    
                    t.Start();
                }
    
                for (int i = 0; i < 3; i++)
                {
                    string name = "小孩_" + (i + 1);
                    Task t = new Task(Consumer, (object)(name));
    
                    t.Start();
                }
    
    
    
            }
            static void Produce(object name)
            {
                while (true&&MaxO>0)
                {
                    int count = -1;
                    int iPos = -1;
                    lock (Sync2)
                    {
                        count = GetQueuesIdx();
                    }
                    if (count > 0&&!IsFull())
                    {
                        bool refTaken = false;
    
                        Monitor.Enter(Sync, ref refTaken);
                        bool isPut = false;
                        try
                        {
                           
                            for (int i = 0; i < count; i++)
                            {
    
                                iPos = QidxBags[i];
                                var q = Queues[iPos];
    
                                if (q.Count < MAX)
                                {
                                    QidxBags.Remove(iPos);
                                    q.Enqueue(Orange.GetOrange());
                                    MaxO -= 1;
                                    Console.WriteLine(name + ":+摘了一个橘子,放入框【" + iPos + "】中");
                                    Console.WriteLine("框一数量:{0},框二数量{1}", Queues[0].Count, Queues[1].Count);
                                    isPut = true;
                                    //唤醒小孩线程
                                    MResetEvent.Set();
                                    break;
                                }
    
                            }
                        }
                        finally
                        {
                            if (refTaken)
                            {
                                if (iPos > -1)
                                {
                                    QidxBags.Add(iPos);
                                }
    
                                Monitor.Exit(Sync);
                                if (!isPut)
                                {
                                    Console.WriteLine("满了");
                                }
                               
                            }
                        }
                    }
                    else
                    {
                        MResetEvent.WaitOne();
                    }
                
                
                }
            }
    
            static void Consumer(object name)
            {
                while (true)
                {
                    int count = GetQueuesIdx();
                    int iPos = -1;
                    if (count > 0&&!IsEmpty())
                    {
                        bool refTaken = false;
                        bool isPut = false;
                        Monitor.Enter(Sync, ref refTaken);
                        try
                        {
                            for (int i = 0; i < count; i++)
                            {
    
                                iPos = QidxBags[i];
                                var q = Queues[iPos];
    
                                if (q.Count >0)
                                {
                                    QidxBags.Remove(iPos);
                                    Orange o = null;
                                    q.TryDequeue(out o);
                                    Console.WriteLine(name + ":+拿了一个橘子,从框【" + iPos + "】中");
                                    Console.WriteLine("框一数量:{0},框二数量{1}", Queues[0].Count, Queues[1].Count);
                                    isPut = true;
                                    //框有容量了,可以放了,所以唤醒被阻塞得工人线程
                                    MResetEvent.Set();
                                    break;
                                }
    
                            }
                        }
                        finally
                        {
                            if (refTaken)
                            {
                                if (iPos > -1)
                                {
                                    QidxBags.Add(iPos);
                                }
                                Monitor.Exit(Sync);
                                if (!isPut)
                                {
    
                                    Console.WriteLine("都空了");
                                  
                                }
                                
                            }
                        }
                    }
                    else
                    {
                        MResetEvent.WaitOne();//阻塞
                    }
                }
            }
        }
    
        public class Orange
        {
            public static Orange GetOrange()
            {
                Random rand = new Random();
                int t = rand.Next(10, 20);
                Thread.Sleep(t);
                return new Orange();
            }
        }
    

    部分结果:

  • 相关阅读:
    C# 对XML操作-实例
    XML
    得到一个随机数组的方法
    Node Redis 小试
    Hexo快速搭建静态博客并实现远程VPS自动部署
    substr.js 字符串切割
    GraphicsMagick 学习笔记
    store.js 跨浏览器的localStorage
    bodyParser中间件的研究
    Sublime Text 使用指南
  • 原文地址:https://www.cnblogs.com/HelloQLQ/p/15836346.html
Copyright © 2020-2023  润新知