Notes on Writing Threaded Programs that Don't Crash

The following is from Microsoft Research - (ThreadsCSharp.pdf)

The most effective rule for avoiding such deadlocks is to have a partial order for the aquistition of locks in your program. In other words, arrange that for any pair of objects {M1, M2}, each thread that needs to have M1 and M2 locked simultaneously does so by locking the objects in the same order.

The basic rule for using mutual exclusion is straightforward: in a multi-threaded program all shared mutable data must be protected by associating it with some object's lock, and you must access the data only from a thread that is holding the lock.

Unsynchronized, or improperly synchronized access becomes increasingly likely as your locking granularity becomes finder and your locking rules become correspondingly more complex. Such problems will arise less often if you use simple, coarse grained locking. For example, use the object instance's lock to protect all instance fields of a class and use typeof(the class) to protect the static fields. Unfortunately very coarse grained locking can cause other problems. So the best advice is to make your locks as simple as possible, but no simpler.

Programmers are often tempted to to skip using the lock, since it introduces significant overhead and they "know" that the variables will be acessed with atomic instructions and that the instructions are not interleaved. This is an exceedingly dangerous assumption. (the compiler is free to monkey with your code quite a bit...also, I've made this mistake before)

A deadlock is a more pleasant bug than returning the wrong answer.

Example of a deadlock:

  1. thread A locks object M1;
  2. thread B locks object M2;
  3. thread A blocks trying to lock M2;
  4. thread B blocks truing to lock M1.

The following is from (IBM)

A reentrant function does not hold static data over successive calls, nor does it return a pointer to static data. All data is provided by the caller of the function. A reentrant function must not call non-reentrant functions.

In C language, local variables are dynamically allocated on the stack. Therefore, any function that does not use static data or other shared resources is trivially thread-safe, as in the following example:

/* thread-safe function */
int diff(int x, int y)
{
int delta;

delta = y - x;
if (delta < 0)
delta = -delta;

return delta;
}

Tools that Detect Threading Errors

  • Coverity