(Post 13/11/2007) Commonly, instances of a
type are thread-safe for concurrent read operations, but not for concurrent
updates (nor a concurrent read and update). This can also be true with
resources such as a file. While protecting instances of such types with
a simple exclusive lock for all modes of access usually does the trick,
it can unreasonably restrict concurrency if there are many readers and
just occasional updates. An example of where this this could occur is
in a business application server, where commonly used data is cached for
fast retrieval in static fields. The ReaderWriterLock class is designed
to provide maximum-availibility locking in just this scenario.
ReaderWriterLock provides separate methods
for acquiring read and write locks – AcquireReaderLock
and AcquireWriterLock. Both methods require a timeout argument, and will
throw an ApplicationException if a timeout occurs (rather than returning
false, like most equivalent methods in threading classes). Timeouts can
occur quite easily if the resource is heavily contended.
A lock is released by calling ReleaseReaderLock
or ReleaseWriterLock. These methods honor nested locking; a ReleaseLock
method is also provided that clear all lock nesting levels in one hit.
(One can subsequently call RestoreLock to re-lock to the same level it
was prior to calling ReleaseLock – so as to mimic Monitor.Wait's lock
toggling behavior).
One can start out with a read-lock by calling AcquireReaderLock
then upgrade to a write-lock by calling UpgradeToWriterLock.
The method returns a cookie that can then be used to then call DowngradeFromWriterLock.
This system allows a reader to temporarily request write-access whilst
not having to re-queue after demotion.
In the following example, four threads are started –
one that continually adds items to a list; another that continually removes
items from the list, and two that continually report the number of items
in the list. The former two obtain write-locks; the latter two obtain
read-locks. A timeout of 10 seconds is used for each lock (exception handlers
are generally included to catch the resultant ApplicationException;
in this example they are omitted for brevity).
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();
}
}
Because adding an item to a List is quicker than removing
it, this example includes a SpinWait in the AppendItem method to help
even things out – keeping the total item count from running away.
(Sưu tầm)
|