通常来讲,一个类型的实例对于并行的读操作是线程安全的,但是并行地更新操作则不是(并 行地读和更新也不是)。 这对于资源也是一样的,比如一个文件。当保护类型的实例安全时,使用一个简单的排它锁即解决问题,但是当有很多的读操作 而偶然的更新操作这就很不合理的限制了并发。一个例子就是这在一个业务程序服务器中,为了快速查找把数据缓存到静态字段中。 在这个方案中,ReaderWriterLock类被设计成提供最大容量的锁定。
ReaderWriterLock为读和写的锁提供了 不同的方法——AcquireReaderLock和AcquireWriterLock。两个方法都需要一个超时参数,并且在超时发生后抛出 ApplicationException异常(不同于大多数线程类的返回false等效的方法)。在资源争用严重的时候发生超时相当容易。
调 用 ReleaseReaderLock或ReleaseWriterLock释放锁。 这些方法支持嵌套锁,ReleaseLock方法也支持一次清除所有嵌套级别的锁。(你可以随后调用RestoreLock类重新锁定相同的级别,它在 ReleaseLock之前执行——如此来模仿Monitor.Wait的锁定切换行为)。
你 可以调用AcquireReaderLock开始一个read-lock ,然后通过UpgradeToWriterLock把它升级为write-lock。这个方法返回一个可能被用于调用 DowngradeFromWriterLock的信息。这个方式允许读程序临时地请求写访问同时不必必须在降级之后重新排队列。
在接 下来的这个例子中,4个线程被启动:一个不停地往列表中增加项目;另一个不停地从列表中移除项目;其它两个不停地报告列表中项目的个数。前两者获得写的 锁,后两者获得读的锁。每个锁的超时参数为10秒。(异常处理一般要使用来捕捉ApplicationException,这个例子中出于方便而省略了)
class Program {
static ReaderWriterLock rw = new ReaderWriterLock ();
static List <int> items = new List <int> ();
static Random rand = new Random ();
static void Main (string[] args) {
new Thread (delegate() { while (true) AppendItem(); } ).Start();
new Thread (delegate() { while (true) RemoveItem(); } ).Start();
new Thread (delegate() { while (true) WriteTotal(); } ).Start();
new Thread (delegate() { while (true) WriteTotal(); } ).Start();
}
static int GetRandNum (int max) { lock (rand) return rand.Next (max); }
static void WriteTotal() {
rw.AcquireReaderLock (10000);
int tot = 0; foreach (int i in items) tot += i;
Console.WriteLine (tot);
rw.ReleaseReaderLock();
}
static void AppendItem () {
rw.AcquireWriterLock (10000);
items.Add (GetRandNum (1000));
Thread.SpinWait (400);
rw.ReleaseWriterLock();
}
static void RemoveItem () {
rw.AcquireWriterLock (10000);
if (items.Count > 0)
items.RemoveAt (GetRandNum (items.Count));
rw.ReleaseWriterLock();
}
}
往List中加项目要比移除快一些,这个例子在AppendItem中包含了SpinWait来保持项目总数平衡。
按照请求到达的顺序,一共有四种
Reader-Reader,第二个不需等待,直接获得读控制权;
Reader-Writer,第二个需要等待第一个调用ReleaseReaderLock()释放读控制权后,才能获得写控制权;
Writer-Writer,第二个需要等待第一个调用ReleaseWriterLock()释放写控制权后,才能获得写控制权;
Writer-Reader,第二个需要等待第一个调用ReleaseWriterLock()释放写控制权后,才能获得读控制权。