Previous | Table of Contents | Next |
11.2.3.4. Threads
Dividing a computation into concurrent processes (or threads of control) is a fundamental method of separating concerns. For example, suppose you are programming a terminal emulator with a blinking cursor: The most satisfactory way to separate the cursor blinking code from the rest of the program is to make it a separate thread. Suppose you are augmenting a program with a new module that communicates over a buffered channel. Without threads, the rest of the program will be blocked whenever the new module blocks on its buffer, and conversely, the new module will be unable to service the buffer whenever any other part of the program blocks. If this is unacceptable (as it almost always is), there is no way to add the new module without finding and modifying every statement of the program that might block. These modifications destroy the structure of the program by introducing undesirable dependencies between what would otherwise be independent modules.
The provisions for threads in Modula-2 are weak, amounting essentially to co-routines. Hoares monitors are a sounder basis for concurrent programming. Monitors were used in Mesa, where they worked well, except that the requirement that a monitored data structure be an entire module was irksome. For example, it is often useful for a monitored data structure to be an object instead of a module. Mesa relaxed this requirement, made a slight change in the details of the semantics of Hoares Signal primitive, and introduced the Broadcast primitive as a convenience. The Mesa primitives were simplified in the Modula-2+ design, and the result was successful enough to be incorporated with no substantial changes in Modula-3.
A threads package is a tool with a very sharp edge. A common programming error is to access a shared variable without obtaining the necessary lock. This introduces a race condition that can lie dormant throughout testing and strike after the program is shipped. Theoretical work on process algebra has raised hopes that the rendezvous model of concurrency may be safer than the shared memory model, but the experience with Ada, which adopted the rendezvous, lends at best equivocal support for this hope; Ada still allows shared variables, and apparently they are widely used.
11.2.3.5. Safety
A language feature is unsafe if its misuse can corrupt the runtime system so that further execution of the program is not faithful to the language semantics. An example of an unsafe feature is array assignment without bounds checking: If the index is out of bounds, then an arbitrary location can be clobbered and the address space can become fatally corrupted. An error in a safe program can cause the computation to abort with a runtime error message or to give the wrong answer, but it cant cause the computation to crash in a rubble of bits.
Safe programs can share the same address space, each safe from corruption by errors in the others. Getting similar protection for unsafe programs requires placing them in separate address spaces. As large address spaces become available, and programmers use them to produce tightly coupled applications, safety becomes more and more important.
Unfortunately, it is generally impossible to program the lowest levels of a system with complete safety. Neither the compiler nor the runtime system can check the validity of a bus address for an I/O controller, nor can they limit the ensuing havoc if it is invalid. This presents the language designer with a dilemma. If he holds out for safety, then low level code will have to be programmed in another language. But if he adopts unsafe features, then his safety guarantee becomes void everywhere.
The languages of the BCPL family are full of unsafe features; the languages of the Lisp family generally have none (or none that are documented). In this area, Modula-3 follows the lead of Cedar by adopting a small number of unsafe features that are allowed only in modules explicitly labeled unsafe. In a safe module, the compiler prevents any errors that could corrupt the runtime system; in an unsafe module, it is the programmers responsibility to avoid them.
11.2.3.6. Garbage Collection
A classic unsafe runtime error is to free a data structure that is still reachable by active references (or dangling pointers). The error plants a time bomb that explodes later, when the storage is reused. If on the other hand the programmer fails to free records that have become unreachable, the result will be a storage leak and the computation space will grow without bound. Problems due to dangling pointers and storage leaks tend to persist long after other errors have been found and removed. The only sure way to avoid these problems is the automatic freeing of unreachable storage, or garbage collection.
Modula-3 therefore provides traced references, which are like Modula-2 pointers except that the storage they point to is kept in the traced heap where it will be freed automatically when all references to it are gone.
Another great benefit of garbage collection is that it simplifies interfaces. Without garbage collection, an interface must specify whether the client or the implementation has the responsibility for freeing each allocated reference and the conditions under which it is safe to do so. This can swamp the interface in complexity. For example, Modula-3 supports text strings by a simple required interface Text, rather than with a built-in type. Without garbage collection, this approach would not be nearly as attractive.
New refinements in garbage collection have appeared continually for more than 20 years, but it is still difficult to implement efficiently. For many programs, the programming time saved by simplifying interfaces and eliminating storage leaks and dangling pointers makes garbage collection a bargain, but the lowest levels of a system may not be able to afford it. For example, in SRCs Topaz system, the part of the operating system that manages files and heavy-weight processes relies on garbage collection, but the inner nub that implements virtual memory and thread context switching does not. Essentially, all Topaz application programs rely on garbage collection.
For programs that cannot afford garbage collection, Modula-3 provides a set of reference types that are not traced by the garbage collector. In most other respects, traced and untraced references behave identically.
Previous | Table of Contents | Next |