20.3 Race Conditions and Deadlocks

The .NET library provides sufficient thread support that you will rarely find yourself creating your own threads and managing synchronization manually.

Thread synchronization can be tricky, especially in complex programs. If you do decide to create your own threads, you must confront and solve all the traditional problems of thread synchronization, such as race conditions and deadlock.

20.3.1 Race Conditions

A race condition exists when the success of your program depends on the uncontrolled order of completion of two independent threads.

Suppose, for example, that you have two threadsone is responsible for opening a file and the other is responsible for writing to the file. It is important that you control the second thread so that it's assured that the first thread has opened the file. If not, under some conditions, the first thread will open the file and the second thread will work fine; under other unpredictable conditions, the first thread won't finish opening the file before the second thread tries to write to it, and you'll throw an exception (or worse, your program will simply seize up and die). This is a race condition, and race conditions can be very difficult to debug.

You cannot leave these two threads to operate independently; you must ensure that Thread1 will have completed before Thread2 begins. To accomplish this, you might Join( ) Thread2 on Thread1. As an alternative, you can use a Monitor and Wait( ) for the appropriate conditions before resuming Thread2.

20.3.2 Deadlock

When you wait for a resource to become free, you are at risk of deadlock, also called a deadly embrace. In a deadlock, two or more threads are waiting for each other, and neither can become free.

Suppose you have two threads, ThreadA and ThreadB. ThreadA locks down an Employee object and then tries to get a lock on a row in the database. It turns out that ThreadB already has that row locked, so ThreadA waits.

Unfortunately, ThreadB can't update the row until it locks down the Employee object, which is already locked down by ThreadA. Neither thread can proceed, and neither thread will unlock its own resource. They are waiting for each other in a deadly embrace.

As described, the deadlock is fairly easy to spotand to correct. In a program running many threads, deadlock can be very difficult to diagnose, let alone solve. One guideline is to get all the locks you need or to release all the locks you have. That is, as soon as ThreadA realizes that it can't lock the Row, it should release its lock on the Employee object. Similarly, when ThreadB can't lock the Employee, it should release the Row. A second important guideline is to lock as small a section of code as possible and to hold the lock as briefly as possible.

    Part I: The C# Language