使用线程安全字典与队列模拟红绿灯通行
最近遇到一道机试题目:场景:在一个十字路口,有红绿灯,有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();
}
好的,编码工作完成,启动程序看效果↓
达到预期效果