I know this has been asked before (and I will keep researching), but I need to know how to make a particular linked list function in a thread safe manner. My current issue is that I have one thread that loops through all elements in a linked list, and another may add more elements to the end of this list. Sometimes it happens that the one thread tries to add another element to the list while the first is busy iterating through it (which causes an exception).
I was thinking of just adding a variable (boolean flag) to say that the list is currently busy being iterated through, but then how do I check it and wait with the second thread (it is ok if it waits, as the first thread runs pretty quickly). The only way I can think of doing this is through the use of a while loop constantly checking this busy flag. I realized this was a very dumb idea as it would cause the CPU to work hard while doing nothing useful. And now I am here to ask for a better insight. I have read about locks and so on, but it does not seem to be relevant in my case, but perhaps I am wrong?
In the meanwhile I'll keep searching the internet and post back if I find a solution.
EDIT:
Let me know if I should post some code to clear things up, but I'll try and explain it more clearly.
So I have a class with a linked list in it that contains elements that require processing. I have one thread that iterates through this list through a function call (let's call it "processElements"). I have a second thread that adds elements to process in a non-deterministic manner. However, sometimes it happens that it tries to call this addElement function while the processElements is running. This means that the an element is being added to the linked list while it is being iterated through by the first thread. This is not possible and causes an exception. Hope this clears it up.
I need the thread that adds new elements to yield until the processElements method is done executing.
To anyone stumbling on this problem. The accepted answer will give you a quick, an easy solution, but check out Brian Gideon's answer below for a more comprehensive answer, which will definitely give you more insight!
The exception is likely the result of having the collection changed in the middle of an iteration via IEnumerator. There are few techniques you can use to maintain thread-safety. I will present them in order of difficultly.
Lock Everything
This is by far the easiest and most trivial method for getting access to the data structure thread-safe. This pattern works well when the number of read and write operations are equally matched.
LinkedList<object> collection = new LinkedList<object>();
void Write()
{
lock (collection)
{
collection.AddLast(GetSomeObject());
}
}
void Read()
{
lock (collection)
{
foreach (object item in collection)
{
DoSomething(item);
}
}
}
Copy-Read Pattern
This is a slightly more complex pattern. You will notice that a copy of the data structure is made prior to reading it. This pattern works well when the number of read operations are few compared to the number of writes and the penalty of the copy is relatively small.
LinkedList<object> collection = new LinkedList<object>();
void Write()
{
lock (collection)
{
collection.AddLast(GetSomeObject());
}
}
void Read()
{
LinkedList<object> copy;
lock (collection)
{
copy = new LinkedList<object>(collection);
}
foreach (object item in copy)
{
DoSomething(item);
}
}
Copy-Modify-Swap Pattern
And finally we have the most complex and error prone pattern. I actually do not recommend using this pattern unless you really know what you are doing. Any deviation from what I have below could lead to problems. It is easy to mess this one up. In fact, I have inadvertently screwed this one up as well in the past. You will notice that a copy of the data structure is made prior to all modifications. The copy is then modified and finally the original reference is swapped out with the new instance. Basically we are always treating collection as if it were immutable. This pattern works well when the number of write operations are few compared to the number of reads and the penalty of the copy is relatively small.
object lockobj = new object();
volatile LinkedList<object> collection = new LinkedList<object>();
void Write()
{
lock (lockobj)
{
var copy = new LinkedList<object>(collection);
copy.AddLast(GetSomeObject());
collection = copy;
}
}
void Read()
{
LinkedList<object> local = collection;
foreach (object item in local)
{
DoSomething(item);
}
}
Update:
So I posed two questions in the comment section:
Why lock(lockobj) instead of lock(collection) on the write side?
Why local = collection on the read side?
Concerning the first question consider how the C# compiler will expand the lock.
void Write()
{
bool acquired = false;
object temp = lockobj;
try
{
Monitor.Enter(temp, ref acquired);
var copy = new LinkedList<object>(collection);
copy.AddLast(GetSomeObject());
collection = copy;
}
finally
{
if (acquired) Monitor.Exit(temp);
}
}
Now hopefully it is easier to see what can go wrong if we used collection as the lock expression.
Thread A executes object temp = collection.
Thread B executes collection = copy.
Thread C executes object temp = collection.
Thread A acquires the lock with the original reference.
Thread C acquires the lock with the new reference.
Clearly this would be disasterous! Writes would get lost since the critical section is entered more than once.
Now the second question was a little tricky. You do not necessarily have to do this with the code I posted above. But, that is because I used the collection only once. Now consider the following code.
void Read()
{
object x = collection.Last;
// The collection may get swapped out right here.
object y = collection.Last;
if (x != y)
{
Console.WriteLine("It could happen!");
}
}
The problem here is that collection could get swapped out at anytime. This would be an incredibly difficult bug to find. This is why I always extract a local reference on the read side when doing this pattern. That ensure we are using the same collection on each read operation.
Again, because problems like these are so subtle I do not recommend using this pattern unless you really need to.
Here’s a quick example of how to use locks to synchronize your access to the list:
private readonly IList<string> elements = new List<string>();
public void ProcessElements()
{
lock (this.elements)
{
foreach (string element in this.elements)
ProcessElement(element);
}
}
public void AddElement(string newElement)
{
lock (this.elements)
{
this.elements.Add(element);
}
}
A lock(o) statement means that the executing thread should acquire a mutual-exclusion lock on the object o, execute the statement block, and finally release the lock on o. If another thread attempts to acquire a lock on o concurrently (either for the same code block or for any other), then it will block (wait) until the lock is released.
Thus, the crucial point is that you use the same object for all the lock statements that you want to synchronize. The actual object you use may be arbitrary, as long as it is consistent. In the example above, we’re declared our collection to be readonly, so we can safely use it as our lock. However, if this were not the case, you should lock on another object:
private IList<string> elements = new List<string>();
private readonly object syncLock = new object();
public void ProcessElements()
{
lock (this.syncLock)
{
foreach (string element in this.elements)
ProcessElement(element);
}
}
public void AddElement(string newElement)
{
lock (this.syncLock)
{
this.elements.Add(element);
}
}
Related
I am trying to find a safe approach for synchronizing access to a nested dictionary where operations on the outer dictionary lock any manipulation to the entire collection. However, once the inner dictionary is retrieved I would like to release the outer lock and only prevent threads from manipulating the inner dictionary.
It is trivial to use the lock keyword for this once the inner dictionary exists within the outer dictionary, however, I am struggling to find a safe approach for adding and populating the inner dictionary without introducing a race condition. Assume "Populating" the inner dictionary will be an expensive operation. This is why doing it under lock for the outer dictionary is not an option. Other threads must have access to the outer dictionary to perform operations on OTHER inner dictionaries while "populate" is executed against the new inner dictionary.
I have included examples below which better illustrate my question.
A trivial approach which I believe introduces a race condition
An approach I believe will work, however, can result in unnecessarily populating multiple inner dictionaries. This will only end up using the first inner dictionary which wins the race.
An approach I believe will work, however, it is unfamiliar territory. Even with extensive testing I would be concerned that my lack of experience using the C#'s Monitor object could lead to unexpected consequences.
Example 1: What I believe to be an unsafe approach to my problem. However, this is how I traditionally synchronization. I would prefer to use this approach, however I do not know of a way to use the lock keyword and achieve the behavior I need.
public void SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
{
Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
lock (outerDictionary)
{
// No need to add a new innerDictionary, it already exists
if (outerDictionary.ContainsKey(newKey))
{
return;
}
innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
outerDictionary.Add(newKey, innerDictionary);
}
// I want to allow other threads to have access to outerDictionary
// However, I don't want other threads working against THIS innerDictionary
lock (innerDictionary)
{
// Here lies my concern with this approach. Another thread could have
// taken the lock for innerDictionary. Doing this all under the lock
// for outerDictionary would be safe but would prevent access to other
// inner dictionaries while expensive operations are performed only
// pertaining to THIS innerDictionary
this.PopulateInnerDictionary(innerDictionary);
}
}
Example 2: An approach using lock that does not encounter the issues portrayed in Example 1. However, not ideal as unnecessary computation may occur if multiple threads attempt the operation at the same time. Note that this approach could be modified to lock against a global "Populate" lock, however, that would prevent multiple threads from concurrently populating different innerDictionaries.
public void SyncronizationExample3(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
{
lock (outerDictionary)
{
if (outerDictionary.ContainsKey(newKey))
{
// No need to add a new innerDictionary, it already exists
return;
}
}
var innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
// Expensive operation - if called by multiple threads at the same time
// multiple innerDictionaries will be populated but only the one to win
// the race will be utilized.
this.PopulateInnerDictionary(innerDictionary);
lock (this.outerDictionary)
{
if (!outerDictionary.ContainsKey(newKey))
{
// If another thread won the race this newKey would be in outerDictionary
// The innerDictionary we just populated is redundant
outerDictionary.Add(newKey, innerDictionary);
}
}
}
Example 3: What I believe to be a solution to the potential synchronization issues demonstrated in Example 1. However, I am unfamiliar with using the Monitor pattern and would greatly appreciate feedback.
public void SyncronizationExample3(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
{
Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
bool aquiredLockForOuterDictionary = false;
bool aquiredLockForInnerDictionary = false;
try
{
Monitor.Enter(outerDictionary, ref aquiredLockForOuterDictionary);
if (outerDictionary.Contains(newKey)
{
// No need to add a new innerDictionary, it already exists
return;
}
innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
outerDictionary.Add(newKey, innerDictionary);
// This is where I "handoff" the lock to innerDictionary to alleviate my concern
// in Example 1 where another thread could steal the innerDictionary lock
Monitor.Enter(innerDictionary, ref aquiredLockForInnerDictionary);
}
finally
{
// I read that this bool pattern was preferred for .net 4+,
// however I am unsure if this is the best practice
if (aquiredLockForOuterDictionary)
{
Monitor.Exit(dictionary);
}
}
try
{
if (!aquiredLockForInnerDictionary)
{
// An exception must have occurred prior to or during the acquisition
// of this lock. Not sure how I'd handle this yet but
// I'm pretty shit out of luck.
return;
}
// Here I would perform an expensive operation against the innerDictionary
// I do not want to lock consumers form accessing other innerDictionaries
// while this computation is done.
this.PopulateInnerDictionary(innerDictionary);
}
finally
{
// I need to check this here incase an exception in the first
// try finally prevented this from being acquired
if (aquiredLockForInnerDictionary)
{
Monitor.Exit(innerDictionary);
}
}
}
Seems like you're overthinking it. The only question I have is, do you have a reliable way of knowing whether an inner dictionary instance has been populated? I would assume that a non-zero value for the Count property would suffice, no?
With that assumption, you can do this:
public Dictionary<TKey2, ReferenceTypedValues> SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
{
Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
lock (outerDictionary)
{
// No need to add a new innerDictionary if it already exists
if (!outerDictionary.TryGetValue(newKey, out innerDictionary))
{
innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
outerDictionary.Add(newKey, innerDictionary);
}
}
// Found the inner dictionary, but might be racing with another thread
// that also just found it. Lock and check whether it needs populating
lock (innerDictionary)
{
if (innerDictionary.Count == 0)
{
this.PopulateInnerDictionary(innerDictionary);
}
}
return innerDictionary;
}
Notes:
It is not a good idea to use the object itself as the lock object, because you run the risk of some other code having the same bad idea and deadlocking with that code. Instead, store a composite value (e.g. Tuple<object, Dictionary<...>>, a custom named struct, etc.) that contains both the object to use for locking, and the inner dictionary itself. (And of course, have a single dedicated object stored in a field for locking the single outer dictionary.)
Your question doesn't describe how these objects are used. I'm guessing once populated, they are read-only? If so, the above should be fine, but you should use the ReadOnlyDictionary class to enforce that. Otherwise, you of course need to add synchronization for that too.
Even assuming they are read-only, your original code doesn't actually return the inner dictionary reference. I've modified the example slightly so that it does. It must. You can't have code looking for the reference without going through the checks in the code above. Best case scenario, it won't be fully populated yet, worst case scenario you'll crash due to trying to access the data structure while it's in an inconsistent state. You could by convention require that callers always call the above method before retrieving the reference value, but if they are going to do that, you might as well make it convenient and return the value from the method itself.
In case we have an immutable object like an ImmutableList(). What is the preferred method for using this object in a multi threaded environment?
Eg
public class MutableListOfObjects()
{
private volatile ImmutableList objList;
public class MutableListOfObjects()
{
objList = new ImmutableList();
}
void Add(object o)
{
// Adding a new object to a list will create a new list to ensure immutability of lists.
// Is declaring the object as volatile enough or do we want to
// use other threading concepts?
objList = objList.Add(o);
}
// Will objList always use that lest version of the list
bool Exist(object o)
{
return objList.Exist(o);
}
}
Is declaring the reference volatile sufficient for achieving the desired behavior? Or is it preferable to use other threading functions?
"Preferred" is contextual. The simplest approach is to use a lock, and in most cases that will do the job very effectively. If you have good reason to think that lock is a problem, then Interlocked is useful:
bool retry;
do {
var snapshot = objList;
var combined = snapshot.Add(o);
retry = Interlocked.CompareExchange(ref objList, combined, snapshot)
!= snapshot;
} while(retry);
This basically works on an optimistic but checked path: most times through, it'll only go through once. Occasionally somebody will change the value of objList while we aren't looking - that's fine, we just try again.
There are, however, pre-canned implementations of thread-safe lists etc, by people who really know what they are talking about. Consider using ConcurrentBag<T> etc. Or just a List<T> with a lock.
A simple and efficient approach is to use ImmutableInterlocked.Update. You pass it a method to perform the add. It calls your add method and then atomically assigns the new value to objList if the list didn't change during the add. If the list changed, Update calls your add method again to retry. It keeps retrying until it is able to write the change.
ImmutableInterlocked.Update(ref objList, l => l.Add(o));
If you have a lot of write contention, such that you'd spend too much time on retries, then using a lock on some stable object (not objList) is preferable.
volatile will not help you in this case - it will not create the lock between reading objList, calling Add() and assigning objList. You should use a locking mechanism instead. volatile just protects against operation reallocations.
In your case you are creating a new list every time an object is added - usually a much better alternative would be to create the list inside a local thread variable (so that it is not subject to multi-threading issues) and once the list is created, mark it as immutable or create a immutable wrapper for it. This way you will get much better performance and memory usage.
I have a System.Collections.Generic.List<T> to which I only ever add items in a timer callback. The timer is restarted only after the operation completes.
I have a System.Collections.Concurrent.ConcurrentQueue<T> which stores indices of added items in the list above. This store operation is also always performed in the same timer callback described above.
Is a read operation that iterates the queue and accesses the corresponding items in the list thread safe?
Sample code:
private List<Object> items;
private ConcurrentQueue<int> queue;
private Timer timer;
private void callback(object state)
{
int index = items.Count;
items.Add(new object());
if (true)//some condition here
queue.Enqueue(index);
timer.Change(TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(-1));
}
//This can be called from any thread
public IEnumerable<object> AccessItems()
{
foreach (var index in queue)
{
yield return items[index];
}
}
My understanding:
Even if the list is resized when it is being indexed, I am only accessing an item that already exists, so it does not matter whether it is read from the old array or the new array. Hence this should be thread-safe.
Is a read operation that iterates the queue and accesses the corresponding items in the list thread safe?
Is it documented as being thread safe?
If no, then it is foolish to treat it as thread safe, even if it is in this implementation by accident. Thread safety should be by design.
Sharing memory across threads is a bad idea in the first place; if you don't do it then you don't have to ask whether the operation is thread safe.
If you have to do it then use a collection designed for shared memory access.
If you can't do that then use a lock. Locks are cheap if uncontended.
If you have a performance problem because your locks are contended all the time then fix that problem by changing your threading architecture rather than trying to do dangerous and foolish things like low-lock code. No one writes low-lock code correctly except for a handful of experts. (I am not one of them; I don't write low-lock code either.)
Even if the list is resized when it is being indexed, I am only accessing an item that already exists, so it does not matter whether it is read from the old array or the new array.
That's the wrong way to think about it. The right way to think about it is:
If the list is resized then the list's internal data structures are being mutated. It is possible that the internal data structure is mutated into an inconsistent form halfway through the mutation, that will be made consistent by the time the mutation is finished. Therefore my reader can see this inconsistent state from another thread, which makes the behaviour of my entire program unpredictable. It could crash, it could go into an infinite loop, it could corrupt other data structures, I don't know, because I'm running code that assumes a consistent state in a world with inconsistent state.
Big edit
The ConcurrentQueue is only safe with regard to the Enqueue(T) and T Dequeue() operations.
You're doing a foreach on it and that doesn't get synchronized at the required level.
The biggest problem in your particular case is the fact the enumerating of the Queue (which is a Collection in it's own right) might throw the wellknown "Collection has been modified" exception. Why is that the biggest problem ? Because you are adding things to the queue after you've added the corresponding objects to the list (there's also a great need for the List to be synchronized but that + the biggest problem get solved with just one "bullet"). While enumerating a collection it is not easy to swallow the fact that another thread is modifying it (even if on a microscopic level the modification is a safe - ConcurrentQueue does just that).
Therefore you absolutely need synchronize the access to the queues (and the central List while you're at it) using another means of synchronization (and by that I mean you can also forget abount ConcurrentQueue and use a simple Queue or even a List since you never Dequeue things).
So just do something like:
public void Writer(object toWrite) {
this.rwLock.EnterWriteLock();
try {
int tailIndex = this.list.Count;
this.list.Add(toWrite);
if (..condition1..)
this.queue1.Enqueue(tailIndex);
if (..condition2..)
this.queue2.Enqueue(tailIndex);
if (..condition3..)
this.queue3.Enqueue(tailIndex);
..etc..
} finally {
this.rwLock.ExitWriteLock();
}
}
and in the AccessItems:
public IEnumerable<object> AccessItems(int queueIndex) {
Queue<object> whichQueue = null;
switch (queueIndex) {
case 1: whichQueue = this.queue1; break;
case 2: whichQueue = this.queue2; break;
case 3: whichQueue = this.queue3; break;
..etc..
default: throw new NotSupportedException("Invalid queue disambiguating params");
}
List<object> results = new List<object>();
this.rwLock.EnterReadLock();
try {
foreach (var index in whichQueue)
results.Add(this.list[index]);
} finally {
this.rwLock.ExitReadLock();
}
return results;
}
And, based on my entire understanding of the cases in which your app accesses the List and the various Queues, it should be 100% safe.
End of big edit
First of all: What is this thing you call Thread-Safe ? by Eric Lippert
In your particular case, I guess the answer is no.
It is not the case that inconsistencies might arrise in the global context (the actual list).
Instead it is possible that the actual readers (who might very well "collide" with the unique writer) end up with inconsistencies in themselves (their very own Stacks meaning: local variables of all methods, parameters and also their logically isolated portion of the heap)).
The possibility of such "per-Thread" inconsistencies (the Nth thread wants to learn the number of elements in the List and finds out that value is 39404999 although in reality you only added 3 values) is enough to declare that, generally speaking that architecture is not thread-safe ( although you don't actually change the globally accessible List, simply by reading it in a flawed manner ).
I suggest you use the ReaderWriterLockSlim class.
I think you will find it fits your needs:
private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private List<Object> items;
private ConcurrentQueue<int> queue;
private Timer timer;
private void callback(object state)
{
int index = items.Count;
this.rwLock.EnterWriteLock();
try {
// in this place, right here, there can be only ONE writer
// and while the writer is between EnterWriteLock and ExitWriteLock
// there can exist no readers in the following method (between EnterReadLock
// and ExitReadLock)
// we add the item to the List
// AND do the enqueue "atomically" (as loose a term as thread-safe)
items.Add(new object());
if (true)//some condition here
queue.Enqueue(index);
} finally {
this.rwLock.ExitWriteLock();
}
timer.Change(TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(-1));
}
//This can be called from any thread
public IEnumerable<object> AccessItems()
{
List<object> results = new List<object>();
this.rwLock.EnterReadLock();
try {
// in this place there can exist a thousand readers
// (doing these actions right here, between EnterReadLock and ExitReadLock)
// all at the same time, but NO writers
foreach (var index in queue)
{
this.results.Add ( this.items[index] );
}
} finally {
this.rwLock.ExitReadLock();
}
return results; // or foreach yield return you like that more :)
}
No because you are reading and writing to/from the same object concurrently. This is not documented to be safe so you can't be sure it is safe. Don't do it.
The fact that it is in fact unsafe as of .NET 4.0 means nothing, btw. Even if it was safe according to Reflector it could change anytime. You can't rely on the current version to predict future versions.
Don't try to get away with tricks like this. Why not just do it in an obviously safe way?
As a side note: Two timer callbacks can execute at the same time, so your code is doubly broken (multiple writers). Don't try to pull off tricks with threads.
It is thread-safish. The foreach statement uses the ConcurrentQueue.GetEnumerator() method. Which promises:
The enumeration represents a moment-in-time snapshot of the contents of the queue. It does not reflect any updates to the collection after GetEnumerator was called. The enumerator is safe to use concurrently with reads from and writes to the queue.
Which is another way of saying that your program isn't going to blow up randomly with an inscrutable exception message like the kind you'll get when you use the Queue class. Beware of the consequences though, implicit in this guarantee is that you may well be looking at a stale version of the queue. Your loop will not be able to see any elements that were added by another thread after your loop started executing. That kind of magic doesn't exist and is impossible to implement in a consistent way. Whether or not that makes your program misbehave is something you will have to think about and can't be guessed from the question. It is pretty rare that you can completely ignore it.
Your usage of the List<> is however utterly unsafe.
I have a list of objects (musical notes) that is enumerated on a separate thread as they are played. I am doing this so that I can keep the UI thread responsive.
whilst a note is playing (as part of an enumeration) how can I allow for the fact that a new note may of been added to the List (without the obvious collection modified exception).
I know I could copy the list to a temporary list and enumerate that, but I actually want the list to grow as a user selects more (and this will happen whilst the first note is playing etc).
psuedo logic as is:
onClick()
{
Queue.Add(theClickedNote)
Queue.Play() <-- on another thread
}
Play()
{
if(Playing==true){return ;}
foreach(note theNote in Queue)
{
Note.Play();
Queue.Remove(theNote);
}
}
As you can see in the above, each Click event adds a note to the Queue and then invokes a play method on the queue.
the queue enumerates the notes and plays each one in turn before removing the note
I hope I have explained what I am trying to do clearly?
Something like this can be used with ConcurrentQueue<T> in .Net 4.
ConcurrentQueue<Note> Queue = new ConcurrentQueue<Note>();
void onClick()
{
Queue.Enqueue(theClickedNote);
// start Play on another thread if necessary
}
void Play()
{
if (Playing) return;
Note note;
while(Queue.TryDequeue(out note))
{
note.Play();
}
}
ConcurrentQueue is thread-safe, so no locking needs to be implemented.
Instead of using a List you should use a real Queue
Then your code will look like this:
Queue<Note> queue = new Queue<Note>();
void onClick()
{
queue.Enqueue(note);
}
void Play()
{
if (Playing == true) { return; }
while (queue.Peek() != null)
{
var note = queue.Dequeue();
note.play();
}
}
This code isn't thread safe so you should add locks on the queue but this is the general idea.
As suggested by mike z, use the ConcurrentQueue added in .NET 4.0
Along with the other concurrent collections, this queue allows you to add / remove items asynchronosly + work with a snapshot of the underlying collection, by using the GetEnumerator method and iterating with it.
Notice you still might need to deal with different situations, such as queue being empty, this can be solved via the BlockingCollection, which take method will block the thread as long as the collection is empty
In my app I have a List of objects. I'm going to have a process (thread) running every few minutes that will update the values in this list. I'll have other processes (other threads) that will just read this data, and they may attempt to do so at the same time.
When the list is being updated, I don't want any other process to be able to read the data. However, I don't want the read-only processes to block each other when no updating is occurring. Finally, if a process is reading the data, the process that updates the data must wait until the process reading the data is finished.
What sort of locking should I implement to achieve this?
This is what you are looking for.
ReaderWriterLockSlim is a class that will handle scenario that you have asked for.
You have 2 pair of functions at your disposal:
EnterWriteLock and ExitWriteLock
EnterReadLock and ExitReadLock
The first one will wait, till all other locks are off, both read and write, so it will give you access like lock() would do.
The second one is compatible with each other, you can have multiple read locks at any given time.
Because there's no syntactic sugar like with lock() statement, make sure you will never forget to Exit lock, because of Exception or anything else. So use it in form like this:
try
{
lock.EnterWriteLock(); //ReadLock
//Your code here, which can possibly throw an exception.
}
finally
{
lock.ExitWriteLock(); //ReadLock
}
You don't make it clear whether the updates to the list will involve modification of existing objects, or adding/removing new ones - the answers in each case are different.
To handling modification of existing items in the list, each object should handle it's own locking.
To allow modification of the list while others are iterating it, don't allow people direct access to the list - force them to work with a read/only copy of the list, like this:
public class Example()
{
public IEnumerable<X> GetReadOnlySnapshot()
{
lock (padLock)
{
return new ReadOnlyCollection<X>( MasterList );
}
}
private object padLock = new object();
}
Using a ReadOnlyCollection<X> to wrap the master list ensures that readers can iterate through a list of fixed content, without blocking modifications made by writers.
You could use ReaderWriterLockSlim. It would satisfy your requirements precisely. However, it is likely to be slower than just using a plain old lock. The reason is because RWLS is ~2x slower than lock and accessing a List would be so fast that it would not be enough to overcome the additional overhead of the RWLS. Test both ways, but it is likely ReaderWriterLockSlim will be slower in your case. Reader writer locks do better in scenarios were the number readers significantly outnumbers the writers and when the guarded operations are long and drawn out.
However, let me present another options for you. One common pattern for dealing with this type of problem is to use two separate lists. One will serve as the official copy which can accept updates and the other will serve as the read-only copy. After you update the official copy you must clone it and swap out the reference for the read-only copy. This is elegant in that the readers require no blocking whatsoever. The reason why readers do not require any blocking type of synchronization is because we are treating the read-only copy as if it were immutable. Here is how it can be done.
public class Example
{
private readonly List<object> m_Official;
private volatile List<object> m_Readonly;
public Example()
{
m_Official = new List<object>();
m_Readonly = m_Official;
}
public void Update()
{
lock (m_Official)
{
// Modify the official copy here.
m_Official.Add(...);
m_Official.Remove(...);
// Now clone the official copy.
var clone = new List<object>(m_Official);
// And finally swap out the read-only copy reference.
m_Readonly = clone;
}
}
public object Read(int index)
{
// It is safe to access the read-only copy here because it is immutable.
// m_Readonly must be marked as volatile for this to work correctly.
return m_Readonly[index];
}
}
The code above would not satisfy your requirements precisely because readers never block...ever. Which means they will still be taking place while writers are updating the official list. But, in a lot of scenarios this winds up being acceptable.