下面模拟一个场景:一辆车有X个座位,设置有限个数的爱心座椅,车门一次只能进入N个乘客,Y个乘客排队上车(Y>X),先上车的人座位自由选择(包含爱心座椅),后上车的人群中如果需要特殊照顾(老弱幼孕等),需要优先安排到爱心座椅,如果没有空余的爱心座椅,则可以坐普通座位,剩下没有座位的人只能站立了!(场景设计可能不那么恰当,纯粹学习之用)如何用多线程来模拟乘客上车找座位这个场景呢?
下面来实现这个效果:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
const int personCount = 30; //设置乘客数
const int siteCount = 25; //设置车座位数
const int threadCount = 3;// 设置一次进入车门的人数
static ReaderWriterLockSlim readWriteLock = new ReaderWriterLockSlim();
static Queue<Passenger> passengerQueue = new Queue<Passenger>();//存储乘客队列
static List<Seat> seatList = new List<Seat>();//座位集合
static CountdownEvent countDown;
static Stopwatch stopWatch = new Stopwatch();
static void Main(string[] args)
{
InitData();//初始化乘客和座位数据
stopWatch.Start();
Console.WriteLine("Stopwatch is running:{0}", stopWatch.IsRunning);
countDown = new CountdownEvent(threadCount);
for (int i = 1; i < threadCount+1; i++)
{
Thread thread1 = new Thread(DispatchPerson);
thread1.Name = "线程"+thread1.ManagedThreadId;
thread1.Start();
}
countDown.Wait();
Console.WriteLine("乘客分配完毕。。。");
countDown.Dispose();
stopWatch.Stop();
Console.WriteLine("Total running time:{0}", stopWatch.ElapsedMilliseconds);
Console.Read();
}
static void DispatchPerson()
{
while (true)
{
readWriteLock.EnterUpgradeableReadLock();
try
{
if (passengerQueue.Count() == 0)
{
Console.WriteLine("{0} 结束...", Thread.CurrentThread.ManagedThreadId);
countDown.Signal();
break;
}
Passenger p = passengerQueue.Dequeue();
Random ran = new Random();
int sc = seatList.Count();
if (sc > 0)
{
Seat targetSeat= new Seat();
int siteIndex = ran.Next(0, sc);
if (p.NeedSpecialCare)
{
// Thread.Sleep(TimeSpan.FromSeconds(1));
targetSeat = seatList.Find(s => s.SpecialCareSite);
if (targetSeat.SeatNo>0)
{
Console.WriteLine("{0}:分配结果:{1},座位:{2} ,爱心座椅(^_^)", Thread.CurrentThread.Name, p.PassengerName, targetSeat.SeatNo);
}
else
{
targetSeat = seatList[siteIndex];
Console.WriteLine("{0}:分配结果:{1},座位:{2} , sorry, 爱心座椅没有了...", Thread.CurrentThread.Name, p.PassengerName, targetSeat.SeatNo);
}
}
else
{
targetSeat = seatList[siteIndex];
Console.WriteLine("{0}:分配结果:{1},座位:{2} ", Thread.CurrentThread.Name, p.PassengerName, targetSeat.SeatNo);
}
readWriteLock.EnterWriteLock();
try
{
seatList.Remove(targetSeat);
}
finally
{
readWriteLock.ExitWriteLock();
}
}
else
{
Console.WriteLine("{0}:分配乘客_>{1},无座了...", Thread.CurrentThread.Name, p.PassengerName);
}
}
finally
{
readWriteLock.ExitUpgradeableReadLock();
}
Thread.Sleep(TimeSpan.FromSeconds(1)); //等待一会,让其他线程有机会参与运算
}
}
static void InitData()
{
for (var i = 1; i <= personCount; i++)
{
Passenger p=new Passenger();
p.PassengerName = String.Format("乘客{0}",i);
if (i % 10 == 0)
p.NeedSpecialCare = true;
passengerQueue.Enqueue(p);
}
for (var i = 1; i <= siteCount; i++)
{
Seat st=new Seat();
st.SeatNo = i;
if (i % 15 == 0)
st.SpecialCareSite = true;
seatList.Add(st);
}
}
}
struct Passenger
{
public string PassengerName { get; set; }
public bool NeedSpecialCare { get; set; }
}
struct Seat
{
public int SeatNo { get; set; }
public bool SpecialCareSite { get; set; }
}
}
运行效果:
上面是学习ReaderWriterLockSlim 后动手实践的一个小例子:ReaderWriterLockSlim代表了一个管理资源访问的锁,允许多个线程同时读取,以及独占写;上面的例子中对有对seatList的读取和删除,本身是线程不安全的,需要使用同步机制。
关于EnterUpgradeableReadLock 方法援引technet.microsoft.com 中的一段描述:
只有一个线程可以在任何给定时间进入可升级模式。 如果某个线程处于可升级模式,并且有没有线程在等待进入写入模式,任意数量的其他线程可以输入读取的模式,即使有线程在等待进入可升级模式。
如果一个或多个线程在等待进入写入模式,线程的调用 EnterUpgradeableReadLock 方法受到阻止,直到这些线程超时或已进入写入模式,然后又从中退出。
分析本人写的这个例子:每个线程在循环后都是要等待进入写入模式,于是调用EnterUpgradeabelReadLock受阻,变相同步了passengerQueue. 如果试图注释掉EnterUpgradeabelReadLock / ExitUpgradeableReadLock 会报错:类似索引超界!
对多线程的学习,目前只是根据测试反馈结果和一些文档上的描述来揣测同步行为,对线程同步背后计算机的逻辑还不是特别清晰,以上如有错误,欢迎指出!