Skip to main content

Conditions For Deadlock

This page is a generated reference surface for selective reading. It exists to keep the learner apps guide-first while still preserving source access.

Learning objectives

  • Explain the main ideas and vocabulary in Conditions For Deadlock.
  • Work through the source examples for Conditions For Deadlock without depending on raw chunk order.
  • Use Conditions For Deadlock as selective reference when learner modules point back to Ostep.

Prerequisites

  • None curated yet.

Module targets

  • module-03-concurrency-synchronization

AI companion modes

  • Explain simply
  • Socratic tutor
  • Quiz me
  • Challenge my understanding
  • Diagnose my confusion
  • Generate extra practice
  • Revision mode
  • Connect forward / backward

Source-of-truth note

This unit is anchored to Ostep and the source chapter "Conditions For Deadlock". Use external resources only to clarify, extend, or modernize details without replacing the chapter's conceptual spine.

External enrichment

No chapter-specific enrichment resources are curated yet. Add them in the unit manifest when a source clearly improves learning.

Source provenance

  • Primary source: Ostep
  • Source chapter: Conditions For Deadlock
  • Raw source file: 156-conditions-for-deadlock.md

Merged source

Conditions For Deadlock

Conditions for Deadlock

Four conditions need to hold for a deadlock to occur [C+71]:

  • Mutual exclusion:Threads claim exclusive control of resources that they require (e.g., a thread grabs a lock).
  • Hold-and-wait:Threads hold resources allocated to them (e.g., locks that they have already acquired) while waiting for additional resources (e.g., locks that they wish to acquire).
  • No preemption:Resources (e.g., locks) cannot be forcibly removed from threads that are holding them.
  • Circular wait:There exists a circular chain of threads such that each thread holds one or more resources (e.g., locks) that are being requested by the next thread in the chain. If any of these four conditions are not met, deadlock cannot occur.

Thus, we first explore techniques topreventdeadlock; each of these strategies seeks to prevent one of the above conditions from arising and thus is one approach to handling the deadlock problem.

Prevention

Circular Wait

Probably the most practical prevention technique (and certainly one that is frequently employed) is to write your locking code such that you never induce a circular wait. The most straightforward way to do that is to provide atotal orderingon lock acquisition. For example, if there are only two locks in the system (L1andL2), you can prevent deadlock by always acquiringL1beforeL2. Such strict ordering ensures that no cyclical wait arises; hence, no deadlock.

Of course, in more complex systems, more than two locks will exist, and thus total lock ordering may be difficult to achieve (and perhaps is unnecessary anyhow). Thus, apartial orderingcan be a useful way to structure lock acquisition so as to avoid deadlock. An excellent real example of partial lock ordering can be seen in the memory mapping code in Linux [T+94]; the comment at the top of the source code reveals ten different groups of lock acquisition orders, including simple ones such as "imutexbeforeimmapmutex" and more complex orders such as "immapmutexbeforeprivatelockbeforeswaplockbefore

mapping->treelock".

As you can imagine, both total and partial ordering require careful design of locking strategies and must be constructed with great care. Further, ordering is just a convention, and a sloppy programmer can easily ignore the locking protocol and potentially cause deadlock. Finally, lock

TIP: ENFORCELOCKORDERINGBYLOCKADDRESS

In some cases, a function must grab two (or more) locks; thus, we know we must be careful or deadlock could arise. Imagine a function that is called as follows: dosomething(mutext *m1, mutext *m2). If the code always grabsm1beforem2(or alwaysm2beforem1), it could deadlock, because one thread could calldosomething(L1, L2)while another thread could calldosomething(L2, L1).

To avoid this particular issue, the clever programmer can use theaddress of each lock as a way of ordering lock acquisition. By acquiring locks in either high-to-low or low-to-high address order, dosomething()can guarantee that it always acquires locks in the same order, regardless of which order they are passed in. The code would look something like this:

if (m1 > m2) { // grab locks in high-to-low address order
pthread_mutex_lock(m1);
pthread_mutex_lock(m2);
} else {
pthread_mutex_lock(m2);
pthread_mutex_lock(m1);
}
// Code assumes that m1 != m2 (it is not the same lock)

By using this simple technique, a programmer can ensure a simple and efficient deadlock-free implementation of multi-lock acquisition.

ordering requires a deep understanding of the code base, and how various routines are called; just one mistake could result in the "D" word1.

Hold-and-wait

The hold-and-wait requirement for deadlock can be avoided by acquiring all locks at once, atomically. In practice, this could be achieved as follows:

lock(prevention);
lock(L1);
lock(L2);
...
unlock(prevention);

By first grabbing the lockprevention, this code guarantees that no untimely thread switch can occur in the midst of lock acquisition and thus deadlock can once again be avoided. Of course, it requires that any time any thread grabs a lock, it first acquires the global prevention lock. For

example, if another thread was trying to grab locksL1andL2in a different order, it would be OK, because it would be holding the prevention lock while doing so.

1Hint: "D" stands for "Deadlock".

Note that the solution is problematic for a number of reasons. As

before, encapsulation works against us: when calling a routine, this approach requires us to know exactly which locks must be held and to acquire them ahead of time. This technique also is likely to decrease concurrency as all locks must be acquired early on (at once) instead of when they are truly needed.