.NET使用线程安全字典与队列模拟红绿灯通行

最近遇到一道机试题目:场景:在一个十字路口,有红绿灯,有5辆车正在由南往北通行,行人是由东往西,有10个人在等待绿灯通行;绿灯时间是45秒,红灯时间是30秒,请考虑使用多线程的方式模拟,车辆运行、红绿灯切换以及行人过街道。

解题思路

见到题目的时候脑海里闪过的就是线程Tread.join(),通过插入其他线程来达到车人通行的切换,亦或者使用线程中创建新线程这种套娃的方式来实现,但是我觉得两种方式若在实际生产环境中并不会这样操作,于是想起了线程安全的字典与队列来模拟这个场景。

线程安全字典模拟红绿灯,队列的入队出队模拟人行和车行的等待。.

(解题肯定有很多解法,做完记录下来,说不定能起到抛砖引玉的作用~)

源代码在文末,需要自取~

线程安全字典ConcurrentDictionary

二话不说先上官网地址:https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=net-5.0

在多线程的情况下使用普通的字典Dictionary,在肯定会导致数据错乱,这个毫无疑问,而且还会遇到一些 迭代时异常,甚至会导致CPU爆高的情况,这个在 《一线码农》 大佬的一篇车联网CPU爆高文章中有分析到,而线程安全字典它能够适应这种场景。

线程安全队列ConcurrentQueue

二话不说先上官网地址:https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.concurrentqueue-1?view=net-5.0

官网定义的ConcurrentQueue为线程程安全的先进先出 (FIFO) 集合,由此来模拟车道与人道等待队列。

不说废话上代码

创建场景

按照面向对象的思想,首先需要创建出红绿灯,车道、人道及其等待队列

 private static ConcurrentDictionary<bool, int> TrafficLightDictionary = new ConcurrentDictionary<bool, int>();
        private static bool _traffic = true;//车行道红绿灯    绿灯True,红灯False   对应人行道    红灯True,绿灯False

        //车道等待队列
        private static ConcurrentQueue<Car> carsQueue = new ConcurrentQueue<Car>();
        //人道等待队列
        private static ConcurrentQueue<Person> personsQueue = new ConcurrentQueue<Person>();

        private readonly static int _carRunTime = 45;//车行45秒
        private readonly static int _personRunTime = 30;//人行30秒

初始化红绿灯

题目中绿灯45秒红灯30秒,没有考虑黄灯的情况,所以用ConcurrentQueue<bool,int>中的bool值作为Key,绿灯True,红灯False,紧接着正好因为车行时,人不能行,人行时车不能行,故可以只使用一个字典就可以满足红绿灯运行条件。

 static void TrafficLightRun(object state)
        {
            TrafficLightDictionary.TryGetValue(_traffic, out int carRunTimeValue);
            TrafficLightDictionary.TryGetValue(!_traffic, out int personTimeValue);

            //Console.WriteLine($"carRunTimeValue:{carRunTimeValue}   personTimeValue:{personTimeValue}"); 测试用

            //复位
            if (carRunTimeValue <= 0 && personTimeValue <= 0)
            {
                TrafficLightDictionary.AddOrUpdate(_traffic, _carRunTime, (_traffic, value) => _carRunTime);
                TrafficLightDictionary.AddOrUpdate(!_traffic, _personRunTime, (_traffic, value) => _personRunTime);
            }

            //车行
            if (carRunTimeValue > 0 && carRunTimeValue <= _carRunTime)
            {
                TrafficLightDictionary.AddOrUpdate(_traffic, carRunTimeValue, (_traffic, value) => --value);
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine($"----当前车行道---绿灯-还剩余{--carRunTimeValue}秒-");
            }
            
            //人行
            else if (personTimeValue > 0 && personTimeValue <= _personRunTime)
            {
                TrafficLightDictionary.AddOrUpdate(!_traffic, personTimeValue, (_traffic, Othervalue) => --Othervalue);
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine($"----当前人行道---绿灯-还剩余{--personTimeValue}秒-");
            }
        }

红绿灯运行逻辑写好之后,创建一个定时器(System.Threading.Tasks.Timer)去定时刷新这个字典,从而控制红绿灯的秒数。

      Console.WriteLine("红绿灯初始化运行!");
      Timer TrafficLightTimer = new Timer(TrafficLightRun);
      TrafficLightTimer.Change(0, 1000);

初始化车流和人流

题目中说有有5辆车,10个人正在通过这个十字路口,参考实际情况中,每一辆车每一个人都是一个新的线程,车和人都在随机的进入这个红绿灯场景,所以使用一个随机数生成随机人与车加入等待队列,形成车流与人流。

        static async void InitCarMode()
        {
            while (true)
            {
                // 车的数量随机
                Random random = new Random();
                var carCount = random.Next(10, 50);
                Console.WriteLine($"推入车流量 {carCount}人");
                List<Task> taskFactories = new List<Task>();
                for (int i = 0; i < carCount; i++)
                {
                    Car inQueueCar = new Car()
                    {
                        Id = Guid.NewGuid().ToString(),
                        Name = "模拟车辆" + i,
                        MaxParallel = 4
                    };
                    Task task = new Task(() =>
                    {
                        CarInQueue(inQueueCar);
                    });
                    taskFactories.Add(task);
                    task.Start();
                }
                await Task.WhenAll(taskFactories.ToArray());
                // Console.WriteLine("--入队完成--");
                await Task.Delay(10 * 1000);
            }
        }
        
        static async void createPersonMode()
        {
            while (true)
            {
                // 人的数量随机
                Random random = new Random();
                var personCount = random.Next(10, 40);
                Console.WriteLine($"推入人流量 {personCount}人");
                List<Task> taskFactories = new List<Task>();
                for (int i = 0; i < personCount; i++)
                {
                    Person inQueuePerson = new Person()
                    {
                        Id = Guid.NewGuid().ToString(),
                        Name = "模拟行人" + i,
                        MaxParallel = 5
                    };
                    Task task = new Task(() =>
                    {
                        PersonInQueue(inQueuePerson);
                    });
                    taskFactories.Add(task);
                    task.Start();
                }
                await Task.WhenAll(taskFactories.ToArray());
                // Console.WriteLine("--入队完成--");
                await Task.Delay(10 * 1000);
            }
        }

考虑到车道会有4车道,人行道也不是排队过马路,所以在车辆与人模型里加入了MaxParallel最大并行度。

车流量或者人流量先全部放到队列里,按照最大并行度去通行

        /// <summary>
        /// 车 入队
        /// </summary>
        static void CarInQueue<T>(T inQueueItem) where T : Car
        {
            carsQueue.Enqueue(inQueueItem);
            //Console.WriteLine("人流 进入等待队列");
        }

        /// <summary>
        /// 人 入队
        /// </summary>
        static void PersonInQueue<T>(T inQueueItem) where T : Person
        {
            personsQueue.Enqueue(inQueueItem);
            //Console.WriteLine("车流 进入等待队列");
        }

初始化车行与人行

车行与人行便是此题的核心逻辑,由于我们已经初始化好了红绿灯的运行,车流人流的队列,其实实现起来就很简单了,主要分已下几步:

1:看红绿灯,是否可以通行,通行时间还有多久——》读取红绿灯的线程安全字典

2:绿灯时,等待队列中是否有车(人),按照每次最大并行量通行——》等待队列持续出队

        static async void CarStart()
        {
            while (true)
            {
                TrafficLightDictionary.TryGetValue(_traffic, out int carRunTime);
                TrafficLightDictionary.TryGetValue(!_traffic, out int personRunTime);

                //Console.WriteLine($"车辆读取value:{CarTime}");
                if (carRunTime > 0)
                {
                    List<Car> carList = new List<Car>();
                    bool run = true;
                    while (run)
                    {
                        if (carsQueue.Count > 0)
                        {
                            carsQueue.TryDequeue(out Car car);
                            carList.Add(car);
                            if (carList.Count == car.MaxParallel)
                            {
                                foreach (var item in carList)
                                {
                                    Console.ForegroundColor = ConsoleColor.Green;
                                    Console.Write($"--车行--Id:{ item.Id}, Name:{ item.Name}--\t");
                                }
                                Console.WriteLine("通行完毕");
                                run = false;
                            }
                            else
                            {
                                TrafficLightDictionary.TryGetValue(_traffic, out int newCarRunTime);
                                if (newCarRunTime <= 0 )
                                {
                                    break;
                                }
                                if (carList.Count>0)
                                {
                                    foreach (var item in carList)
                                    {
                                        Console.ForegroundColor = ConsoleColor.Green;
                                        Console.Write($"--车行--Id:{ item.Id}, Name:{ item.Name}--\t");
                                    }
                                }
                            }

                        }
                    }
                }
                await Task.Delay(500);
            }
        }

篇幅考虑,就没有把人通行代码加入文章中,代码逻辑与车行一致,后续会把源码贴上。

至此已经把车、人行的逻辑都加上了,现在就是用一个新的线程将他们的程序跑起来。

        static void Main(string[] args)
        {
            TrafficLightDictionary.AddOrUpdate(_traffic, _carRunTime, (_traffic, _greenLightTime) => _greenLightTime);
            TrafficLightDictionary.AddOrUpdate(!_traffic, _personRunTime, (_traffic, _redLightTime) => _redLightTime);
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("红绿灯初始化运行!");
            Timer TrafficLightTimer = new Timer(TrafficLightRun);
            TrafficLightTimer.Change(0, 1000);



            Task.Factory.StartNew(InitCarMode);
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("初始化车流 模型 -完成");
            Task.Factory.StartNew(createPersonMode);
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("初始化人流 模型 -完成");

            Thread.Sleep(1000);

            Task.Factory.StartNew(CarStart);
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("初始化车辆通行 -完成");
            Task.Factory.StartNew(PersonStart);
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("初始化行人通行 -完成");
            Console.ReadLine();
        }

好的,编码工作完成,启动程序看效果↓

.NET使用线程安全字典与队列模拟红绿灯通行