在《.NET 4.0面向对象编程漫谈 》的《应用篇》一书中,我介绍了一个使用“信号量(Semaphore) ”同步对象模拟多人使用图书馆公共计算机的UseLibraryComputer示例(参看17.3.3节《管理多个共享资源--Semaphore》):
此示例程序定义了一个Computer类代表计算机,使用一个Computer对象数组(取名LibraryComputers)保存图书馆所拥有的多台公共计算机(即Computer对象)。
示例程序动态创建多个线程模拟多用户(每个线程代表一个用户)同时访问3个Computer对象,为了同步这些线程,创建了一个Semaphore对象(取名sp),以下为示例中的线程函数:
static void UseComputer(Object UserName)
{
sp.WaitOne();//等待计算机可用
//查找可用的计算机
Computer cp=null;
for (int i = 0; i < ComputerNum; i++)
if (LibraryComputers[i].IsOccupied == false)
{
cp = LibraryComputers[i];
break;
}
//使用计算机工作
cp.Use(UserName.ToString());
//不再使用计算机,让出来给其他人使用
sp.Release();
}
Computer类中定义了一个IsOccupied字段用于标识此计算机是否被占用,其Use方法模拟表示用户正在使用计算机的过程:
//是否被占用
public bool IsOccupied = false;
//读者在使用计算机
public void Use(String userName)
{
Console.WriteLine("{0}开始使用计算机{1}", userName,ComputerName);
IsOccupied = true; //设置占用标记
Thread.Sleep(new Random().Next(1, 2000)); //随机休眠,以模拟人使用计算机
Console.WriteLine("{0}结束使用计算机{1}", userName,ComputerName);
IsOccupied = false; //设置空闲标记
}
前述示例代码中隐藏着一个BUG,请看图2:
BUG隐藏于何处?
请读者先仔细阅读原示例程序 ,尝试着自己定位并修正此BUG。
以下是BUG分析:
这里面的关键是Semaphore对象可能会一次允许多个线程投入运行,而这些投入运行的线程都需要访问LibraryComputers数组(请参看前面的UseComputer静态方法)。
假设有3台计算机空闲,当示例程序第1次运行时,会有3个线程同时访问LibraryComputers数组,完全有可能两个线程都发现第1台计算机是“空闲”的,于是它们将调用此Computer对象的Use()方法。
这就是BUG所在。
定位BUG之后,要修正它就很容易了--互斥访问LibraryComputers数组即可 。
修改后的线程函数如下:
static void UseComputer(Object UserName)
{
sp.WaitOne();//等待计算机可用
Computer cp=null; //查找可用的计算机
lock (LibraryComputers)
{
for (int i = 0; i < ComputerNum; i++)
if (LibraryComputers[i].IsOccupied == false)
{
cp = LibraryComputers[i];
cp.IsOccupied = true; //设置已被使用标记
break;
}
}
cp.Use(UserName.ToString());//使用计算机工作
sp.Release();//不再使用计算机,让出来给其他人使用
}
注意:上述代码中不仅添加了锁,还将修改Computer对象占用标记的代码(原先在Computer.Use方法中)也移到这里来了。
以下修改后的Computer.Use()方法:
public void Use(String userName)
{
Console.WriteLine("{0}开始使用计算机{1}", userName,ComputerName);
Thread.Sleep(new Random().Next(1, 2000)); //随机休眠,以模拟人使用计算机
Console.WriteLine("{0}结束使用计算机{1}", userName,ComputerName);
IsOccupied = false;
}
最后留给读者一个思考题:
Computer.Use()方法中修改IsOccupied字段的那句代码需要锁定本对象吗?
============================================================================
附注:
此示例程序中的BUG由CSDN网友“笑傲江湖 ”发现,是本人在设计示例时的疏漏,在此特别感谢“笑傲江湖" 网友的热心!也欢迎各位读者或业界朋友多指正本人拙著中的错误,互相探讨技术,共同进步。