Fork me on GitHub

线程同步介绍及 生产者消费者问题举例 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();
        }
    }

部分结果:

 

 

posted @ 2022-01-23 14:28  HelloLLLLL  阅读(331)  评论(0编辑  收藏  举报