使用執行緒安全字典與佇列模擬紅綠燈通行
最近遇到一道機試題目:場景:在一個十字路口,有紅綠燈,有5輛車正在由南往北通行,行人是由東往西,有10個人在等待綠燈通行;綠燈時間是45秒,紅燈時間是30秒,請考慮使用多執行緒的方式模擬,車輛執行、紅綠燈切換以及行人過街道。
解題思路
見到題目的時候腦海裡閃過的就是執行緒Tread.join(),通過插入其他執行緒來達到車人通行的切換,亦或者使用執行緒中建立新執行緒這種套娃的方式來實現,但是我覺得兩種方式若在實際生產環境中並不會這樣操作,於是想起了執行緒安全的字典與佇列來模擬這個場景。
執行緒安全字典模擬紅綠燈,佇列的入隊出隊模擬人行和車行的等待。
(解題肯定有很多解法,做完記錄下來,說不定能起到拋磚引玉的作用~)
執行緒安全的字典ConcurrentDictionary
在多執行緒的情況下使用普通的字典Dictionary,在肯定會導致資料錯亂,這個毫無疑問,而且還會遇到一些 迭代時異常,甚至會導致CPU爆高的情況,這個在 《一線碼農》 大佬的一篇車聯網CPU爆高文章中有分析到,而執行緒安全字典它能夠適應這種場景。
執行緒安全的佇列ConcurrentQueue
二話不說先上官網地址:
官網定義的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();
}
好的,編碼工作完成,啟動程式看效果↓
達到預期效果