Synchronization is a relatively simple way to
eliminate sharing problems, but it must be used very carefully.
The purpose of synchronization is to let tasks run
safely in parallel, but it does this by not letting them run in parallel when
it would be unsafe. A task that is waiting for a lock is not doing any work at
Also, acquiring and releasing locks take a
non-trivial amount of time. It is easy to write tasks that spend more time
doing synchronization than doing useful work.
Taken together, these two issues mean that you need
to be careful how much you use synchronization. Synchronization should be used
carefully to solve specific problems. If you find yourself synchronizing large
portions of your tasks, you may need to rethink your task structure so that you
can get useful tasks that can run safely without so much synchronization.
One strategy is for a task to synchronize its
import of the data it needs into private memory locations, work on this private
data, and then synchronize the export of the results.
The final problem with synchronization is the
danger of deadlocks. A deadlock happens when one or more threads cannot make
progress. This can happen, for example, when a task has acquired one lock and
is trying to acquire another, while another task has acquired this second lock
and is trying to acquire the first. This situation is called
, and it could cause a program to hang forever.
After adding synchronization, to see whether your
changes have solved the problems, run the Dependencies tool again. This may
reveal previously hidden and newly introduced problems. For example, after you
add locks, run the Dependencies tool again to make sure you have not
accidentally introduced deadlocks into your program or unbalanced pairs of
annotations. The Dependencies tool can detect potential deadlocks because in
addition to memory data accesses, it observes the lock events when the
annotated program runs.