Next: Cancelling Operations
Up: Guidelines for Multithreaded Interfaces
Previous: Sharing Mutable State
Although most operating systems provide only a single thread of control
within each address space, application programs must often deal with a
variety of asynchronous events. As a consequence, many operating systems
have evolved a set of ad hoc techniques for multiplexing the single thread
within an address space. These techniques have the disadvantage that they
add complexity to applications and confuse programmers. To eliminate the
ad hoc techniques, multiple threads can be used, resulting in simpler,
more reliable applications.
The aim of all the ad hoc multiplexing techniques is to avoid blocking
during a particular call on an operating system procedure when the client
thread could be doing other useful work (computing, or calling a different
procedure). Most of the techniques involve replacing a single operating
system procedure that performs a lengthy operation with separate methods
for initiating the operation and for determining its outcome. The typical
methods for determining the outcome of such an asynchronous operation
include:
- Polling.
- Testing whether or not the operation has completed, as by
checking a status field in a control block that is set by the operation.
Polling is useful when the client thread wants to overlap computation with
one or more asynchronous operations. The client must punctuate its
computation with periodic calls to the polling procedure; busy waiting
results when the client has no other useful computation. Note that busy
waiting is undesirable only when there is a potential for the processor to
be used by another process.
- Waiting.
- Calling a procedure that blocks until the completion of a
specified operation, or more usefully one of a set of operations. Waiting
procedures are useful when the client thread is trying to overlap a
bounded amount of computation with one or more asynchronous operations,
and must avoid busy waiting.
- Interrupts.
- Registering a procedure that is called by borrowing
the program counter of the client thread, like a hardware interrupt.
Interrupts are useful in overlapping computation with asynchronous
operations. They eliminate busy waiting and the inconsistent response
times typical of polling. On the other hand, they make it difficult to
maintain the invariants associated with variables that must be shared
between the main computation and the interrupt handler.
The techniques are often combined. The VMS $QIO service is capable of
reporting I/O completion via any combination of the following: setting a
status field, unblocking a wait on an event flag, and delivering an AST
(software interrupt) [5]. Polling, waiting, and
interrupt mechanisms are also used in 4.2BSD UNIX. When an open file has
been placed in non-blocking mode, the read and write operations
return an error code if a transfer is not currently possible.
Non-blocking mode is augmented with two ways to determine a propitious
time to attempt another transfer. The select call waits until a
transfer is possible on one of a set of open files. When an open file has
been placed in asynchronous mode, the system sends a signal (software
interrupt) when a transfer on that file is possible.
When multiple threads are available, it is best to avoid all these
techniques and instead model each operation as a single, synchronous
procedure. This is simple for naive clients, and allows more
sophisticated clients to use separate threads to overlap lengthy system
calls and computation.
Next: Cancelling Operations
Up: Guidelines for Multithreaded Interfaces
Previous: Sharing Mutable State
Paul McJones
8/28/1997