I’m attempting to use the ReaderWriterLockSlim class to manage a list.
There are many reads to this list and few writes, my reads are fast whereas my writes are slow.
I have a simple test harness written to check how the lock works.
If the following situation occurs
Thread 1 - Start Write
Thread 2 - Start Read
Thread 3 - Start Write
Then the outcome is as follows
Thread 1 starts its write and locks the list.
Thread 2 adds itself to the read queue.
Thread 3 adds itself to the write queue.
Thread 1 finishes writing and releases the lock
Thread 3 aquires the lock and starts its write
Thread 3 finishes writing and releases the lock
Thread 2 performs its read
Is there any way of changing the behaviour of the lock so that any Read requests that were queued before a write lock are allowed to complete before the write locks are granted?
EDIT: The code that demonstrates the issue I have is below
public partial class SimpleLock : System.Web.UI.Page
{
public static ReaderWriterLockSlim threadLock = new ReaderWriterLockSlim();
protected void Page_Load(object sender, EventArgs e)
{
List<String> outputList = new List<String>();
Thread thread1 = new Thread(
delegate(object output)
{
((List<String>)output).Add("Write 1 Enter");
threadLock.EnterWriteLock();
((List<String>)output).Add("Write 1 Begin");
Thread.Sleep(100);
((List<String>)output).Add("Write 1 End");
threadLock.ExitWriteLock();
((List<String>)output).Add("Write 1 Exit");
}
);
thread1.Start(outputList);
Thread.Sleep(10);
Thread thread2 = new Thread(
delegate(object output)
{
((List<String>)output).Add("Read 2 Enter");
threadLock.EnterReadLock();
((List<String>)output).Add("Read 2 Begin");
Thread.Sleep(100);
((List<String>)output).Add("Read 2 End");
threadLock.ExitReadLock();
((List<String>)output).Add("Read 2 Exit");
}
);
thread2.Start(outputList);
Thread.Sleep(10);
Thread thread3 = new Thread(
delegate(object output)
{
((List<String>)output).Add("Write 3 Enter");
threadLock.EnterWriteLock();
((List<String>)output).Add("Write 3 Begin");
Thread.Sleep(100);
((List<String>)output).Add("Write 3 End");
threadLock.ExitWriteLock();
((List<String>)output).Add("Write 3 Exit");
}
);
thread3.Start(outputList);
thread1.Join();
thread2.Join();
thread3.Join();
Response.Write(String.Join("<br />", outputList.ToArray()));
}
}
What about avoiding the use of locks almost entirely? During a write you can acquire a lock, copy the original data structure, modify the copy, and then publish the new data structure by swapping out the old reference with the new reference. Since you never modify the data structure after it has been “published” then you would not need to lock reads at all.
Here is how it works:
The trick we are exploiting here is the immutability of whatever is reference by the
datavariable. The only thing we need to do is to mark the variable asvolatile.Note that this trick only works if the writes are sufficiently infrequent enough and the data structure is small enough to keep the copy operation cheap. It is not the be all end all solution. It is not ideal for every scenario, but it may just work for you.