Subtle Multithreading Bugs - Part 1

There are many aspects to multithreaded software design and development that can make it very difficult. I have come across several subtle bugs that can easily bite you if you are not careful. Since many developers may never find a “do not do this” guide that includes these subtleties, I will be explaining them with the hope of helping you improve your multithreading capabilities.

Part 1 is in regards to working with thread-safe collections. There are many ways to make a thread safe collection, such as rolling your own, deriving from a collection and wrapping access to the underlying collection via locks, or using the thread-safe framework collections. We will use the framework’s thread-safe queue in today’s example.

Queue myThreadsafeQueue = Queue.Synchronized(new Queue());

That is how easy it is to make a thread-safe queue. It is just as easy to make a thread-safe list using ArrayList.Synchronized. Sorry, there are no built in thread-safe generic collections (but it is not too difficult to create your own by deriving from a generic collection). So what is a thread-safe collection? It basically means that if you were to execute the following from two different threads, the collection will internally lock on an object and only allow one method to execute at a time:

myThreadsafeQueue.Enqueue(new object());

All method and property calls can therefore be safely called from multiple threads. The real danger that can be difficult to see is when a collection needs to be enumerated. Consider the following way to work on a queue:

while (myThreadsafeQueue.Count > 0)
{
object myObject = myThreadsafeQueue.Dequeue();
//do some work with myObject
}

Do you see the bug? I’m sure the veterans do. The flaw is that between the call to get the Count and the call to Dequeue() an object, another thread could have cleared out the queue. This would mean that when the above code executes, the call to Dequeue() will occur with an empty queue and will thus throw an exception. The solution is to lock on the collection’s SyncRoot object while enumerating:

lock (myThreadsafeQueue.SyncRoot)
{
while (myThreadsafeQueue.Count > 0)
{
object myObject = myThreadsafeQueue.Dequeue();
//do some work with myObject
}
}

Since the SyncRoot object is the object that the collection internally uses to perform synchronization, if the Clear() method is called on the collection from another thread, that thread and method call will block until the Lock(myThreadsafeQueue.SyncRoot) finishes executing. The general rule is as follows:

“Any time a collection is being enumerated, to maintain thread safety, all collection operations should be wrapped in a lock using the SyncRoot object.”

If this rule is not followed, weird exceptions can occur. For example, if you do a ‘foreach’ loop on a thread-safe ArrayList and another thread modifies the collection, you can get the error:

InvalidOperationException: “Collection was modified; enumeration operation may not execute.”

A sample is attached that demonstrates the correct and incorrect way to access a queue safely from multiple threads. Note: If you do decide to derive from a generic collection, it is best to internally lock using the already existing ICollection.SyncRoot object.

Sample: SubtleMultithreadingBugs

Comments are closed.