Next: Avoiding Ad Hoc Multiplexing
Up: Guidelines for Multithreaded Interfaces
Previous: Guidelines for Multithreaded Interfaces
It is not uncommon for a single-threaded interface to reference a state
variable that affects one or more procedures of the interface. The
purpose is often to shorten calling sequences by allowing the programmer
to omit an explicit argument from each of a sequence of procedure calls in
exchange for occasionally having to set the state variable. To avoid
interference over such a state variable, multiple client threads must
often serialize their calls on procedures of the interface even when there
is no requirement for causal ordering between the threads.
One example of interference caused by an interface state variable is the
stream position pointer within a UNIX open file [14]. The
pointer is implicitly read and updated by the stream-like read and
write procedures and is explicitly set by the seek procedure.
If two threads use this interface to make independent random accesses to
the same open file, they have to serialize all their seek-read and
seek-write sequences. Another example is the UNIX library routine
ctime, which returns a pointer to a statically allocated buffer
containing its result and so is not usable by concurrent threads.
While it is important to avoid unnecessary serialization of clients of an
interface, serialization within the implementation of a multithreaded
interface containing shared data structures is often necessary. This is
to be expected and will often consist of fine-grain locking that minimizes
interference between threads.
We can think of four basic approaches to designing multithreaded
interfaces so as to minimize the possibility of interference between
client threads over shared mutable state:
- 1.
- Make it an argument. This is the most general solution, and has the
advantage that one can maintain more than one object of the same type
as the shared mutable state being replaced. In the file system
example, passing the stream position pointer as an argument to
read and write solves the problem. Or consider a
pseudo-random number generator with a large amount of hidden state.
Instead of making the client synchronize its calls on the generator,
or even doing the synchronization within the generator, either of
which may slow down the application, a better solution is to allow
more than one generator state and to pass a reference to that state to
the generator on each call.
- 2.
- Make it a constant. It may be that some state component need not
change once an application is initialized. An example of this might be
the user on whose behalf the application is running.
- 3.
- Let the client synchronize. This is appropriate for mutable state
components that are considered inherently to affect an entire application,
rather than to affect a particular action being done by a single thread.
- 4.
- Make it thread-dependent, by having the procedure use the identity
of the calling thread as a key to look up the variable in a table. Adding
extra state associated with every thread adds to the cost of threads, and
so should not be considered lightly. Having separate copies of a state
variable can also make it more difficult for threads to cooperate in
manipulating a single object.
It is a matter of judgment which of these techniques to use in a
particular case. We used each of the four in designing the Topaz
operating system interface. Sometimes providing a combination offers
worthwhile flexibility. For example, a procedure may take an optional
parameter that defaults to a value set at initialization time. Also, it
is possible for a client to simulate thread-dependent behavior using a
procedure taking an explicit parameter in conjunction with an
implementation of a per-thread property list (set of tag-value pairs).
Next: Avoiding Ad Hoc Multiplexing
Up: Guidelines for Multithreaded Interfaces
Previous: Guidelines for Multithreaded Interfaces
Paul McJones
8/28/1997