Lecture 11: Deadlock mitigation
review, quiz
-
deadlock prevention strategies
-
preemption
-
lock ordering
-
key terms: optimistic concurrency, rollback, livelock
deadlock detection
-
deadlock avoidance: banker鈥檚 algorithm
-
key terms: safe state
Deadlock prevention
We can design a system to avoid deadlock by making any of the 4 conditions impossible.
Breaking mutual exclusion
In some cases, deadlock can be mitigated by making resources more shareable. For example, using a reader/writer lock instead of a mutex can make deadlock less likely (since many readers can share the read lock). Using a lock-free data structure is another way to allow multiple threads to access a data structure simultaneously (without blocking).
However, many resources are inherently non-shareable (e.g. printers: can鈥檛 print two documents simultaneously!). Mutual exclusion is a good condition to break if you can, but often you can鈥檛.
Breaking no-preemption
In some situations we can make resources preemptable. If a process tries to acquire a resource that is held by another process, we can make it possible for the new process to steal the resource.
In order to do this, we need some mechanism for rollback: we need to be able to restore whatever program invariants that the resource was held in order to satisfy.
For example, if the resource is a lock protecting a shared variable, we could roll back the thread that holds the lock by restoring the state of the shared variable to the state it held before the lock was acquired, and restarting the process that was performing the update.
Once we allow computations to be rolled back, we introduce the possibility that two threads can continue to preempt each other forever. Although the system is not deadlocked (both threads seem to be making forward progress), the system may never actually finish its tasks. This state is called livelock: when competing threads are continuously being rolled back before they can finish.
It is not possible to make all resource preemptible. I/O is a well-known impediment to rollback: once some output has been performed, it may be impossible to return to a consistent state. Once you tell the user you鈥檝e started processing their order, you can鈥檛 take it back.
Breaking hold-and-wait
Can break hold-and-wait by having threads release all locks and re-acquire them all at once.
Releasing locks may require rollback, which leads to the same issues described above.
Monitors partially use this strategy to avoid hold-and-wait: calling wait on a condition variable automatically releases the lock, so that acquiring the monitor lock cannot cause deadlock. However, it is still possible to create a form of deadlock with a monitor where one thread needs to wait for some predicate before updating state in a way that satisfies another predicate, while a second thread waits for the second predicate before making the first predicate true.