views:

468

answers:

8

When dealing with threads (specifically in C++) using mutex locks and semaphores is there a simple rule of thumb to avoid Dead Locks and have nice clean Synchronization?

+1  A: 

Read Deadlock: the Problem and a Solution.

"The common advice for avoiding deadlock is to always lock the two mutexes in the same order: if you always lock mutex A before mutex B, then you'll never deadlock. Sometimes this is straightforward, as the mutexes are serving different purposes, but other times it is not so simple, such as when the mutexes are each protecting a separate instance of the same class".

Prasoon Saurav
+11  A: 

A good simple rule of thumb is to always obtain your locks in a consistent predictable order from everywhere in your application. For example, if your resources have names, always lock them in alphabetical order. If they have numeric ids, always lock from lowest to highest. The exact order or criteria is arbitrary. The key is to be consistent. That way you'll never have a deadlock situation. eg.

  1. Thread 1 locks resource A
  2. Thread 2 locks resource B
  3. Thread 1 waits to obtain a lock on B
  4. Thread 2 waits to obtain a lock on A
  5. Deadlock

The above can never happen if you follow the rule of thumb outlined above. For a more detailed discussion, see the Wikipedia entry on the Dining Philosophers problem.

Asaph
+2  A: 

Try to avoid acquiring one lock and trying to acquire another. This can result into circular dependency and cause for deadlock. If it is un-avoidable then at least the order of acquire locks should be predictable.

Use RAII ( to make sure lock is release properly in case of exception as well)

aJ
That doesn't sound practical. There are lots of situations where you need to hold more than one lock at a time. This is especially true if you use granular locks instead of one global one.
Steve Rowe
One way to make this more practical is to use an API that allows you to acquire a set of locks at the same time. If you know you need locks A, B and C, call lock(A, B, C), and the function will attempt to lock all of them, and do so in the correct order. You can write such a function yourself, to centralize the code for acquiring locks in the right order.
jalf
why is not practical? It is more safe to release the mutex call before entering into another component ( there by trying to acquire another lock). "Hold and Wait" is the one of the basic reason for deadlock
aJ
aJ: Plenty of problems require you to acquire multiple locks. It is not practical to only ever hold one lock at a time.
jalf
Its difficult but is it really impractical to design like that?
aJ
It's not practical to design like that and still be performant. While there may be ways that escape me, the usual way to do more with fewer locks is to make the locks more broad. This is fine until you have a lot of threads, then they block too often and performance tanks. Global locks are a solution to deadlocks, but thye are death to performance.
Steve Rowe
+5  A: 
  1. If at all possible, design your code so that you never have to lock more then a single mutex/semaphore at a time.
  2. If that's not possible, make sure to always lock multiple mutex/semaphores in the same order. So if one part of the code locks mutex A and then takes semaphore B, make sure that no other part of the code takes semaphore B and then locks mutex A.
R Samuel Klatchko
+1 Exactly the answer I was going to give. Best concurrent programming advice I've ever heard.
ceretullis
+1  A: 

There is no simple deadlock cure.

Acquire locks in agreed order: If all calls acquire A->B->C then no deadlock can occur. Deadlocks can occur only if the locking order differs between the two threads (one acquires A->B the second B->A).

In practice is hard to choose an order between arbitrary objects in memory. On a simple trivial project is possible, but on large projects with many individual contributors is very hard. A partial solution is to create hierarchies, by ranking the locks. All locks in module A have rank 1, all locks in module B have rank 2. One can acquire a lock of rank 2 when helding locks of rank 1, but not vice-versa. Of course you need a framework around the locking primitives that tracks and validates the ranking.

Remus Rusanu
In C++, choosing an order is very simple. Just order by memory address. In C# or Java, it is trickier as the address of an object is not exposed to the programmer, and may change during the object's lifetime.
jalf
I wish things would be so easy... If you are in the rare position that the code has a list of objects to lock a,b,c yes, you can compare the addresses. But most times a is locked in a function f(), b is locked in g() and c is locked din h() and none of these functions know about the other. It is kind of unrealistic to assume in an application that all locks will be acquired in blocks, knowing what all locks will be and a reference to each.
Remus Rusanu
A: 

One way to ensure the ordering that other folks have talked about is to acquire locks in an order defined by their memory address. If at any point, you try to acquire a lock that should have been earlier in the sequence, you release all the locks and start over.

With a little work, it's possible to do this nearly automatically with some wrapper classes around the system primitives.

Mark Bessey
A: 

There's no practical cure. Specifically, there's no way to simply test code for being synchronizationally correct, or to have your programmers obey the rules of the gentleman with the green V.

There's no way to properly test the multithreaded code, because the program logic may depend on timing of locks acquisition, and therefore, be different from execution to execution, somehow invalidating the concept of QA.

I would say

  • prefer using threads only as a performance optimization for multi-core machines
  • only optimize performance when you are sure you need this performance
  • you may use threads to simplify program logic, but only when you are absolutely sure what you are doing. Be extra careful and all locks are confined to a very small piece of code. Do not let any newbies near such code.
  • never use threads in a mission-critical system, such as flying an aircraft or operating dangerous machinery
  • in all cases, threads are seldom cost-effective, due to higher debug and QA costs

If you determined to do threads or maintaining existing codebase:

  • confine all locks to small and simple pieces of code, which operate on primitives
  • avoid function calls or getting the program flow away to where the fact of being executed under lock is not immediately visible. This function will change by future authors, widening your lock span without your control.
  • get locks inside objects to reduce locking scope, wrap non-thread-safe 3rd-party objects with your own thread-safe interfaces.
  • never send synchronous notifications (callbacks) when executing under lock
  • use only RAII locks, to reduce the cognitive load when thinking "how else can we exit from here", as in exceptions, etc.

A few words on how to avoid multi-threading.

A single-threaded design usually involves some heart-beat function provided by program components, and called in a loop (called heartbeat cycle) which, when called, gives a chance to all components to do the next piece of work and to surrender control back again. What algorithmists like to think of as "loops" inside the components, will turn into state machines, to identify what is the next thing that should be done when called. State is best maintained as member data of respective objects.

Pavel Radzivilovsky
Trying to bolt multithreading onto an app *after* it has been written is a recipe for failure. If you're going to use threads, do it properly, and think it into the app's architecture from day 1. Otherwise you *will* get all the nasty deadlocks and race conditions. And let's be honest, unless you're still living in the 80's, any application that pretends to be even a bit CPU-intensive (or just has to be responsive) can't really avoid threading.
jalf
A: 

There are plenty of simple "deadlock cures". But none that are easy to apply and work universally.

The simplest of all, of course, is "never have more than one thread".

Assuming you have a multithreaded application though, there are still a number of solutions:

You can try to minimize shared state and synchronization. Two threads that just run in parallel and never interact can never deadlock. Deadlocks only occur when multiple threads try to access the same resource. Why do they do that? Can that be avoided? Can the resource be restructured or divided so that for example, one thread can write to it, and other threads are asynchronously passed the data they need?

Perhaps the resource can be copied, giving each thread its own private copy to work with?

And as already mentioned by every other answer, if and when you try to acquire locks, do so in a global consistent order. To simplify this, you should try to ensure that all the locks a thread is going to need are acquired as a single operation. If a thread needs to acquire locks A, B and C, it should not make three lock() calls at different times and from different places. You'll get confused, and you won't be able to keep track of which locks are held by the thread, and which ones it has yet to acquire, and then you'll mess up the order. If you can acquire all the lock you need once, then you can factor it out into a separate function call which acquires N locks, and does so in the correct order to avoid deadlocks.

Then there are the more ambitious approaches: Techniques like CSP make threading extremely simple and easy to prove correct, even with thousands of concurrent threads. But it requires you to structure your program very differently from what you're used to.

Transactional Memory is another promising option, and one that may be easier to integrate into conventional programs. But production-quality implementations are still very rare.

jalf