Deadlock

3
Deadlocks: Race conditions are typically cured by adding a lock that protects the invariant that might otherwise be violated by interleaved operations. Unfortunately, locks have their own hazards, most notably deadlock. Deadlock can occur only if the following four conditions hold true: 1. Access to each resource is exclusive. 2. A thread is allowed to hold one resource while requesting another. 3. No thread is willing to relinquish a resource that it has acquired. 4. There is a cycle of threads trying to acquire resources, where each resource is held by one thread and requested by anot her. Deadlock can be avoided by breaking any one of these conditions. Often the best way to avoid deadlock is to replicate a resource that requires exclusive access, so that each thread can have its own private copy. Each thread can access its own copy without needing a lock. The copies can be merged into a single shared copy of the resource at the end if necessary. By eliminating locking, replication avoids deadlock and has the further benefit of possibly improving scalability, because the lock that was removed might have been a source of contention. If there is no obvious ordering of locks, a solution is to sort the locks by address. This approach requires that a thread know all locks that it needs to acquire before it acquires any of them. For instance, perhaps a thread needs to swap two containers pointed to by pointers x and y, and each container is protected by a lock. The thread could compare “x < y” to determine which container comes first, and acquire the lock on the first container before acquiring a lock on the second container. Locks Ordered by their Addresses void AcquireTwoLocksViaOrdering( Lock& x, Lock& y ) { assert( &x!=&y );

Transcript of Deadlock

Page 1: Deadlock

Deadlocks:

Race conditions are typically cured by adding a lock that protects the invariant that might otherwise be violated by interleaved operations. Unfortunately, locks have their own hazards, most notably deadlock.

Deadlock can occur only if the following four conditions hold true:1. Access to each resource is exclusive.2. A thread is allowed to hold one resource while requesting another.3. No thread is willing to relinquish a resource that it has acquired.4. There is a cycle of threads trying to acquire resources, where each resource is held by one thread and requested by anot her.

Deadlock can be avoided by breaking any one of these conditions. Often the best way to avoid deadlock is to replicate a resource that requires exclusive access, so that each thread can have its own private copy. Each thread can access its own copy without needing a lock.

The copies can be merged into a single shared copy of the resource at the end if necessary. By eliminating locking, replication avoids deadlock and has the further benefit of possibly improving scalability, because the lock that was removed might have been a source of contention.

If there is no obvious ordering of locks, a solution is to sort the locks by address. This approach requires that a thread know all locks that it needs to acquire before it acquires any of them.

For instance, perhaps a thread needs to swap two containers pointed to by pointers x and y, and each container is protected by a lock.

The thread could compare “x < y” to determine which container comes first, and acquire the lock on the first container before acquiring a lock on the second container.

Locks Ordered by their Addressesvoid AcquireTwoLocksViaOrdering( Lock& x, Lock& y ) {

assert( &x!=&y );

if( &x<&y ) {

acquire x

acquire y

} else {

acquire y

acquire x

}

}

Page 2: Deadlock

The third condition for deadlock is that no thread is willing to give up its claim on a resource. Thus another way of preventing deadlock is for a thread to give up its claim on a resource if it cannot acquire the other resources.

For this purpose, mutexes often have some kind of “try lock”routine that allows a thread to attempt to acquire a lock, and give up if it cannot be acquired. This approach is useful in scenarios where sorting the locks is impractical.

Try and back off logicvoid AcquireTwoLocksViaBackoff( Lock& x, Lock& y ) {

for( int t=1; ; t*=2 ) {

acquire x

try to acquire y

if( y was acquired ) break;

release x

wait for random amount of time between 0 and t

}

}

Try and back off logic has some timing delays in it to prevent the hazard of live lock. Live lock occurs when threads continually conflict with each other and back off.

It applies exponential backoff to avoid live lock. If a thread cannot acquire all the locks that it needs, it releases any that it acquired and waits for a

random amount of time. The random time is chosen from an interval that doubles each time the thread backs off. Eventually, the threads involved in the conflict will back off sufficiently that at least one will

make progress. The disadvantage of backoff schemes is that they are not fair. There is no guarantee that a

particular thread will make progress. If fairness is an issue, then it is probably best to use lock ordering to prevent deadlock.