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 threads—one 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 spot—and 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.
|