Deadlock
Lecture Notes for CS 140
Winter 2012
John Ousterhout
- Readings for this topic from Operating System Concepts:
Sections 7.1-7.4.
- The deadlock problem:
- Threads often need to hold multiple locks at the
same time.
- Simple example:
Thread A Thread B
lock_acquire(l1); lock_acquire(l2);
lock_acquire(l2); lock_acquire(l1);
... ...
lock_release(l2); lock_release(l1);
lock_release(l1); lock_release(l2);
- Deadlock definition:
- A collection of threads are all blocked.
- Each thread is waiting for a resource owned
by one of the other threads.
- Since all threads are blocked, none can release
their resources.
- Four conditions for deadlock:
- Limited access: resources cannot be shared.
- No preemption. Once given, a resource cannot be taken away.
- Multiple independent requests: threads don't ask
for resources all at once (hold resources while waiting).
- A circularity in the graph of requests and ownership.
- Complexities:
- Deadlock can occur over anything that causes waiting:
- Locks
- Network messages
- Disk drive
- Memory space exhausted
- Deadlock can occur over separate resources (e.g. locks)
or pieces of a single resource (pages of memory).
- In general, don't know in advance which resources a
thread will need.
- Solution #1: deadlock detection
- Determine when system is deadlocked
- Break the deadlock by terminating one of the threads
- Usually not practical in operating systems, but
often used in database systems where a transaction
can be retried
- Solution #2: deadlock prevention: eliminate one of the
necessary conditions for deadlock
- Don't allow exclusive access? Not reasonable for most
applications.
- Create enough resources so that they never run out?
May work for things like disk space, but locks for
synchronization are intentionally limited in number.
- Allow preemption? Works for some resources but not others
(e.g., can't preempt a lock).
- Require threads to request all resources at the same time;
either get them all or wait for them all.
- Tricky to implement: must wait for several things
without locking any of them.
- Inconvenient for thread: hard to predict needs in advance.
May require thread to over-allocate just to be safe.
- Break the circularity: all threads request resources in the
same order (e.g., always lock l1 before l2).
This is the most common approach used in operating systems.