上周五,一个同学问我一个问题:The Roller Coaster Problem,如何编程实现。
我理解了问题本身的内涵,但要求用C#的信号量SemaphoreSlim的API搞定,我当时有点懵了。曾经学操作系统的时候,一定是睡过去了……
而且在工作时,大部分时间做前端开发,很少关注多线程处理高并发的场景。
周六要加班,没办法,只能等到周日,花了点时间研究,终于写出来可运行的代码。
参考资料:
微软的官方文档:
https://docs.microsoft.com/en-us/dotnet/api/system.threading.semaphoreslim?view=net-5.0
有一本不错的书叫 《The Little Book of Semaphores》,是一本免费的书,作者是Allen B. Downey。里面有一个章节是讲过山车问题的,可以在这里下载:
http://greenteapress.com/semaphores/LittleBookOfSemaphores.pdf
这里有中文翻译:
https://blog.csdn.net/booksyhay/article/details/82897462
在密歇根理工大学(MTU)的网站上,也有一篇文章讲过山车问题的,是用C++写的代码。
https://pages.mtu.edu/~shene/NSF-3/e-Book/SEMA/TM-example-roller.html
实现代码:
实际上,那个同学说的问题,跟网上能找到的资料还有点差别,看来老师还是精心准备过的。
我在实现时,也考虑到了一些复杂情况,尽量都能模拟到。
using System;
using System.Threading;
namespace TestSem
{
class Program
{
public static int rollerCoasterCapacity = 4;
public static int passengersInLine = 9; // 共9个人,最后一轮,只有一人上车
public static int noOfRides = (int)Math.Ceiling((decimal)passengersInLine / (decimal)rollerCoasterCapacity); // 总共跑几圈
public static int currentRide = 0; // 当前的圈数
public static int passengersInCar = 0; // 在车上的乘客数量
// 乘客登入结束。如果某一轮的乘客满足应乘人数,则立即发车
private static SemaphoreSlim passengersBoadingOver = new SemaphoreSlim(0, rollerCoasterCapacity);
// 该上的乘客,全都上车,达到应乘人数
private static SemaphoreSlim allAboard = new SemaphoreSlim(0);
// 开车
private static SemaphoreSlim ridingSem = new SemaphoreSlim(0);
// 开始下车
private static SemaphoreSlim startExitSem = new SemaphoreSlim(0, rollerCoasterCapacity);
// 全部下车
private static SemaphoreSlim allExited = new SemaphoreSlim(0, rollerCoasterCapacity);
static void Main(string[] args)
{
// 过山车有自己的一个线程
Thread thrd = new Thread(new ThreadStart(RollerCoster));
thrd.Start();
// 给每个乘客创建其专有的线程
for (int i = 0; i < passengersInLine; i++)
{
Thread passengerThread = new Thread(new ThreadStart(Passenger));
passengerThread.Name = "Passenger_" + i;
Thread.Sleep(500);
passengerThread.Start();
}
}
private static void Passenger()
{
string passengerName = Thread.CurrentThread.Name;
Console.WriteLine("乘客 {0} 开始等车", passengerName);
passengersBoadingOver.Wait(); // 等待上车的指示
Console.WriteLine("{0}开始上车", passengerName);
Thread.Sleep(500);
Console.WriteLine("{0}已经上车", passengerName);
Interlocked.Increment(ref passengersInCar);
//passengersInCar++; // 注意,多线程环境下,不要这样做。这样会使结果不准确
Console.WriteLine("当前车上的人数: " + passengersInCar);
int passengersCount = passengersCountOfTheRide();
if (passengersBoadingOver.CurrentCount == 0 &&
passengersInCar == passengersCount)
{
allAboard.Release();
Console.WriteLine("达到应乘人数{0},可开车了", passengersCount);
}
// 坐在车上等着开动,以及到站后下车。这个过程中,不需要做任何事情
ridingSem.Wait();
// 阶段3: 等车到站
Console.WriteLine("乘客{0}得到通知车开了,不能下车", passengerName);
startExitSem.Wait();
Console.WriteLine("乘客{0}得到停车信号,开始下车", passengerName);
Interlocked.Decrement(ref passengersInCar);
// 阶段4,通知车子,已经下完
Console.WriteLine(Thread.CurrentThread.Name + ". After get off passengersInCar " + passengersInCar);
if (passengersInCar.Equals(0))
{
Console.WriteLine("车上的人都下完了###");
allExited.Release();
}
// 下车是每个人独自进行的,全部下完后,才能继续上人。
}
private static int passengersCountOfTheRide()
{
int result = rollerCoasterCapacity;
if (currentRide == noOfRides)
{
result = rollerCoasterCapacity % currentRide;
}
return result;
}
private static void RollerCoster()
{
Console.WriteLine("RollerCoster程序开始执行,共需要跑{0}圈", noOfRides);
while (true)
{
//车辆停好,通知乘客上车
if (currentRide == noOfRides)
{
Console.WriteLine("所有rides跑完,结束");
return;
}
currentRide++;
// 如果是最后一圈,则要计算一下最后一圈可以上的人数是多少。
int passengersCount = passengersCountOfTheRide();
Console.WriteLine("跑第{0}圈,应乘人数:{1}", currentRide, passengersCount);
passengersBoadingOver.Release(passengersCount);
passengersInCar = 0;
Console.WriteLine("All Entered");
Thread.Sleep(1500);
// 阶段2,等待乘客上车
allAboard.Wait();
Console.WriteLine("All Boarded");
Thread.Sleep(1500);
// 阶段3,车子行驶阶段
ridingSem.Release(rollerCoasterCapacity); // 某一轮行驶开始
Console.WriteLine("Ride Begins");
Thread.Sleep(1500);
// 阶段4,下车阶段
startExitSem.Release(rollerCoasterCapacity); // 通知所有乘客下车
allExited.Wait();
Console.WriteLine("乘客已下完,某一轮ride结束");
}
}
}
}