Information Technology Reference
In-Depth Information
Often, the best practice is to start simple, often with a single-lock per shared
object. If the objects' interfaces are well designed, then refactoring their im-
plementations to increase concurrency and performance can be done once the
system is built and performance measurements have identified the bottlenecks.
\It is easier to go from a working system to a working, fast system than to go
from a fast system to a fast, working system."
Example: Linux evolution. The first versions of Linux ran only on unipro-
cessor machines. To allow Linux to run on multiprocessor machines, version
2.0 introduced the Big Kernel Lock (BKL)—a single lock that protected all of the
kernel's shared data structures. The BKL allowed the kernel to function on mul-
tiprocessor machines, but scalability and performance were limited. So, over
time, different subsystems and different data structures got their own locks, al-
lowing them to be accessed without holding the BKL. By version 2.6, Linux has
been highly optimized to run well on multiprocessor machines—Linux now has
thousands of different locks and researchers have demonstrated scalability for
a range of benchmarks on a 48 processor machine. Still, the BKL remains in
use in a few—mostly less performance-critical—parts of the Linux kernel like
the reboot() system call, some older file systems, and some device drivers.
6.1.1
Solutions and design patterns
As noted above, there are no completely general solutions for how to structure
multi-object and multi-lock programs. However, there are approaches and de-
sign patterns that work well in practice. We discuss four examples here and
more examples after we have addressed deadlock, which also affects techniques
for writing multi-object, multi-lock programs.
Careful class design
It can be easier to reason about sequences of operations on objects than to reason
about sequences of atomic reads and writes of memory because we often have
control over the interface to those objects. Careful class and interface design
can make it feasible to reason about the overall program. This need for careful
design includes the design of individual objects (e.g., specifying clean interfaces
that expose the right abstractions). It also includes the architecture of how
those objects interact (e.g., structuring a system architecture in well-defined
layers.)
Example: Too much milk. For example, as just noted, it would be dif-
ficult to solve Too Much Milk with a Note object and Fridge object with in-
terfaces Note::readNote() , Note::writeNote() , Fridge::checkForMilk() ,
and Fridge::addMilk() . On the other hand, if we refactor the objects so that
we have Fridge::checkforMilkAndSetNoteIfNeeded() and Fridge::addMilk() ,
the problem is straightforward.
Search WWH ::




Custom Search