1. 程式人生 > 其它 >使用執行緒安全字典與佇列模擬紅綠燈通行

使用執行緒安全字典與佇列模擬紅綠燈通行

最近遇到一道機試題目:場景:在一個十字路口,有紅綠燈,有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();
        }

好的,編碼工作完成,啟動程式看效果↓

達到預期效果

原始碼地址:https://gitee.com/yi_zihao/TransportationTest