1 Toward an architectural pattern language for multi-threading

22 downloads 8706 Views 95KB Size Report
Colorado Technical University. 4435 N. Chestnut Street, Colorado Springs, CO 80907-3896. Phone: (719) 590-6733. Email: [email protected]. Abstract.
Toward an architectural pattern language for multi-threading Dr. Bo I. Sandén Colorado Technical University 4435 N. Chestnut Street, Colorado Springs, CO 80907-3896 Phone: (719) 590-6733. Email: [email protected] Abstract A design pattern is a grouping of a small number of objects that is likely to be useful again and again. In multi-threaded software, it is a grouping of active and passive objects. A pattern language is a system of patterns organized in a structure that guides their application. This paper pro-

poses a pattern language intended to capture the best practices in the architectural design of multi-threaded software. The language consists of a number of well-known patterns and is intended to be sufficient for a class of applications. It enforces a restrictive use of threads and an intuitive architecture based on concurrency in the problem domain.

1. Introduction With the emergence of Java, Windows N/T and other systems, more programmers than ever are confronted with threads and concurrency. While many are still preoccupied with various standard primitives such as mutexes, the interest in design should increase as these primitives become more generally understood. Accomplished programmers will step back from the "code", look at their programs at a more abstract level and recognize design patterns that can profitably be reused.

A pattern is a grouping of a small number of objects that is likely to be useful over and over. It describes a family of solutions to recurring problems. While the classic "gang of four" book [3] documents patterns for "everyday business use", this article applies the pattern approach to multi-threaded designs where the constituents are passive and active objects. The patterns proposed here are well known. In fact, patterns are typically not invented. Instead, recurring patterns are identified in software designs and “made explicit, codified and applied appropriately to similar problems” [14]. What is non-trivial and possibly controversial is that the proposed set of patterns is intended to be sufficient for a class of multi-threaded applications. It discourages other ways of representing, say, resource contention than by means of the patterns Shared Resource or Assembly Line.

1

It can be argued that such a pattern language restricts the programmer's freedom to use threading as well as other language constructs in innovative ways. Not all programming should have the character of such free research, however. If we want to think of software engineering as a mature engineering discipline, design and construction should in all normal cases be based on standard, proven models.

1.1 Design patterns and pattern languages The "gang-of-four" book [3] includes a catalog of 23 general-purpose patterns. They are "descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context". A pattern has four essential elements: the name, the problem, the solution and the consequences. Intent is usually also stated, although it has been argued that it should not be; the recorder of a pattern should not restrict its future uses [4]. In this spirit, intent has been removed from the patterns described here.

A pattern language is a system of patterns organized in a structure that guides their application [9]. It is a way to concretely represent an architectural philosophy. To a degree, pattern languages are alternatives to traditional, prescriptive methods that impose step-wise design processes. A novice may feel secure holding on to such a process, but more accomplished programmers may find it confining and may be unwilling to trust that it will lead mechanically to a suitable architecture for any problem. In contrast, a pattern language enforces an architectural philosophy without imposing a process.

The pattern language proposed here is restrictive. This is in line with current work on software architectures in general: There is no reason to invent new ways to do what can be done in a more understandable, established way. To quote the architect Mies van der Rohe, "less is more". Model-View-Controller [9] is another example of a restrictive pattern language that requires each user task to be handled by a different window, allows only a few classes of panes and disallows hierarchical menus.

1.2 Concurrent patterns Multi-threading on a single processor is sometimes referred to as pseudo-parallelism, suggesting that it merely mimics real, hardware parallelism. But multi-threading can increase performance and throughput on a single processor. This is because most threads give up the

2

processor and wait for various external inputs, such as data from a disk or from a user. In order to use the hardware optimally we must know what processing assignments are ready to be executed whenever the processor becomes available. Some work assignments are not ready since they depend on the completion of other assignments. So, to bring some order to the myriad work assignments that will ultimately be completed, they can be partially ordered. A thread is an ordered set of assignments.

For the purposes of processor utilization, many sequential structures in a problem can be the basis for threads. This gives the designer the freedom to choose a structure that is also conceptually useful. Threading can simplify the object interaction compared to a purely eventdriven design. Awad et al [1] suggest a fairly involved process for breaking a complex object collaboration diagram into separate threads. The approach here achieves the same result by patterning the threads on concurrency in the problem domain.

Especially on a single processor, it may be important to ensure that the concurrent solution is not slower than a sequential one. More tasks certainly do not guarantee more optimum use of the processor, since additional context switches add to the CPU load.

1.3 Concurrency primitives Java is one of few practical languages that provide language support for multi-threading [11]. Apart from the tie-in with object-orientation, Java's concurrency primitives are quite traditional. Another industry-strength language that provides support for multi-threading is Ada. Its primitives are somewhat more elaborate, less error prone and intended for safety-critical applications.

A Java thread can be created either by extending the class Thread or by implementing the interface Runnable. A thread can suspend itself for a given period of time by means of the method sleep. In addition to threads, Java provides synchronized objects with monitor-like behavior. Exclusion synchronization is accomplished by specifying a method as synchronized. A caller of a synchronized method acquires a lock on the object, thereby prohibiting other threads from accessing any synchronized method of that object. Condition synchronization is accomplished by means of a wait call in the statement: while (!condition) wait ( );

3

This statement is placed in the beginning of a synchronized method and reached once a thread has acquired the lock on the object. If the condition is false, the thread releases its lock and is placed in a waiting state until notified by another thread that the condition is true. The method O.notify releases one thread waiting for the object, O, so that it may reattempt access. The call notifyAll releases all threads waiting for the object.

2. An architectural philosophy for multi-threading The architectural philosophy proposed here has earlier been published as entity-life modeling (ELM) [17, 18, 19, 22]. Its goal is simple and easily maintained thread architectures, based on simple and intuitive principles such as a thread per user, a thread per call in a telephone switch application, etc. This is intended to give a clear mental picture of the software in problemdomain terms: the software is driven by the user thread or telephone-call thread, which requires the services of passive objects. Just as with objects and classes, most threads are identified in the problem domain: Each control thread (as in Java) is based on a domain thread.

This restrictive use of concurrency can be more carefully defined as follows: A software system must react to (or create) events in the problem environment. (Each event is without duration but requires a finite amount of processing time.) The analyst attributes the events occurring in a given problem to different concurrent domain threads as follows:

1. Create an imaginary trace by laying out all events that the software has to deal with along a time line. Include both stimuli to which the software must react, and actions that the software must take. 2. Partition the trace into domain threads, which are sets of events such that: a) each event belongs to exactly one thread, and b) the events within each thread are separated by sufficient time for the processing of each event.

The philosophy can also be expressed as a checklist that can be applied to a given design: 1. Is each event handled by one thread? 2. Are simultaneous events handled by different threads?

4

The set of domain threads resulting from the partitioning described above is called a thread model of the problem. A set of domain threads is said to co-occur if there is a point in time when they all have an event. A thread model is called minimal if all its threads co-occur, that is, there is a point in time where an event occurs in each thread. All minimal thread models of a given problem have the same number of domain threads. This number is the concurrency level of the problem and equals the maximum number of events that can ever occur simultaneously.

2.1 Domain threads and control threads Basically, each domain thread maps onto a control thread in the software. If the threads are based on a thread model of the problem, each event is handled by one control thread, and no control thread is inundated with simultaneous events. Such simultaneous events are instead taken care of by different threads.

Each thread model for a given problem is potentially the basis for a software architecture for that problem. The designer may choose among the different models. Minimality is a safeguard against excessive and counter-productive concurrency in the implementation. In a minimal design, there is a time when all threads are busy. A minimal model results in the smallest number of threads, but there may be good reasons to choose a non-minimal model. In some cases, more control threads than domain threads are needed. This happens when an event cannot be attributed to a thread without further analysis of its associated data. In addition to the threads, the design contains synchronized objects that are shared between the threads, and other objects that are used exclusively by single threads.

2.2 Threads and active objects Each domain thread can usually be associated with an active object in the problem, such as an elevator, a robot arm, etc. The domain thread is then the “life” of that object. In a multi-elevator system, for example, events include the arrivals and departures of elevators at different floors. We can then identify one domain thread per elevator since the events associated with it are spaced in time. On the other hand, we cannot include all events in the multi-elevator system in one thread, since several events associated with different elevators may occur simultaneously. For the same reason, we cannot associate a domain thread with each floor, since several elevators may arrive at or depart from a given floor at the same time.

5

2.3 Threads and use cases A use case is a sequence of operations on different objects that are initiated by an external actor toward a particular goal [7]. A use case is often (but not necessarily) a dialog between the system and a human operator. It has limited extension in time. A use case is a series of transactions, each of which completes when the system waits for input stimulus from the actor. An example is a jukebox with a use case “Request_Songs” where a Customer actor inserts money and selects one or more songs, which are subsequently played. Actors are sometimes inanimate entities in the problem domain. In the jukebox example, we may want to include an electro-mechanical actor, Player, that successively plays the songs that have been entered into a request queue by various customers.

Rather than instantiating an object and a thread for each actor, it is often convenient to let a thread correspond to a series of actors. Instead of a thread for each jukebox customer, we may let an active Panel object represent the series of customers that use the jukebox panel next to a particular table in a diner. The corresponding control thread executes the use case Request_Songs over and over.

2.4 Patterns for individual threads The patterns in this section are used to identify individual threads. They are: Sleeper, Dialog and Resource User. Patterns involving interacting threads and objects are introduced in section 3.

2.4.1 Sleeper Motivation and applicability. Many active objects rely on timing events such as "X seconds have passed since event Y" in order to take periodic action or to time out some expected event. Structure. A single thread with sleep calls. Consequences. The pattern avoids dedicated timer threads by putting the timing into "worker" threads. Implementation and examples. The sleeper pattern is easily modeled in software based on the sleep method. Many threads take periodic action. A sampler periodically polls some quantity.

6

Related patterns. Timer (rejected; 4.1)

7

2.4.2 Dialog Motivation and applicability. Many systems include a user dialog, which represents a sequential thread of events. Structure. A single thread that communicates with a user or series of users. Implementation and examples. Dialog is used in interactive systems and in transaction applications, such as supermarket checkout systems, bank teller applications, airline reservation systems, etc., where essentially all events are initiated by the checkers, tellers or reservation agents. It is also common in telecommunications, where each telephone call gets a thread [2, 25]. A more recent invention is the customer interaction system, which is essentially a sophisticated telephone answering machine used in applications such as automated help desks. Depending on the callers' successive choices, the customer interaction system plays back prerecorded voice messages, makes recordings or transfers calls. Each call gets a thread. Each panel in the jukebox example has a dialog thread.

2.4.2 Resource User Motivation and applicability. Many threads need to compete with other threads for exclusive access to some shared resource. Structure. A single thread that obtains exclusive access to one or more passive objects. Consequences. Identifying resource-user threads can simplify object interaction. In an automated switchyard, the event that a train, S, leaves a track segment leads to the release of that segment. An object that controls the queue of waiting trains must then be accessed to find any waiting train, T, say, which must be set in motion. With separate resource-user threads for each train, the chain of calls by S's thread stops with a notify call. This activates T's thread, which takes the necessary further actions. Implementation and examples. Situations where active objects wait for access to shared resources are easily implemented based on wait and notify calls. Related patterns. There are two patterns for resource sharing: Shared Resource and Assembly Line. Shared Resource builds directly on Resource User.

3. The pattern language The goal of the pattern language is force the solutions to concurrent problems into certain canonical forms. There should be no need for another way of representing a state machine than State Machine or for another way to represent a situation with shared resources than Shared

8

Resource or Assembly Line. This is not trivial. In some real problems, the resource sharing situation, for example, may not be immediately apparent. One example is the home heater problem, which is discussed in 3.1.7.

The patterns rely on two basic building blocks: a control thread and a shared, synchronized object. Other authors have contributed patterns for concurrent systems, such as the Active Object [10] and various patterns for communication between threads [11]. The patterns discussed here are of a more architectural nature than these. They are relevant for real-time applications and other concurrent applications such as simulation, animation and multi-media. Some common patterns that were rejected for not conforming to the design philosophy are listed in section 4. In the following diagram, arrows show pattern dependency: The pattern at the head of an arrow relies on the pattern at the tail.

Sleeper Assembly Line

Resource user

Shared Resource

State Machine

Simult. exclusive access

Resource User, Sleeper and Dialog were discussed above. Shared Resource captures the situation where multiple threads need exclusive access to a shared object. State Machine [21] captures the various constellations of threads and objects that can be used to represent a state machine and associated activities. Assembly Line is an alternative resource-sharing pattern where the resources are the threads. Simultaneous Exclusive Access is an extension of

9

the Shared Resource for the case where threads need simultaneous exclusive access to multiple resources.

3.1. Shared Resource 3.1.1 Motivation Shared Resource gives resource-user threads exclusive access to some resource, represented as a synchronized object. Sometimes, the object represents a pool of equivalent resources from which each thread needs one. In another case, each thread needs exclusive access to a piece of a larger shared resource. Another important distinction has to do with the extent in time of the exclusive access. Exclusion synchronization only prevents threads from accessing a shared data item at the same time and thereby jeopardizing data integrity. The probability that a thread will have to wait for access to the resource is usually very low. With condition synchronization, a thread waits for some condition to change. Such a wait is often long enough to be noticeable in the problem environment.

3.1.2 Applicability The shared resource pattern is applicable in all concurrent programs where multiple threads need exclusive access to one or more resources.

3.1.3 Structure

U1

U2

R1

R2

3.1.4 Participants U1 and U2 are resource users. R1 and R2 are resources.

3.1.5 Collaborations Ui operates on Rj as indicated by the direction of the arrow. For each resource R, the exclusive access can be either hidden or public:

10

With hidden exclusive access, each synchronized operation, O, contains the critical section where R is manipulated, i.e., a caller U has exclusive access to R only while executing O. The exclusive access is then hidden inside O.

With public exclusive access, separate synchronized operations are used to acquire and release exclusive access. Acquire has a synchronization condition letting calling threads in only when the resource is available. When a caller, U, regains control, it has exclusive access to the resource. The actual exclusive access occurs in U, which ultimately relinquishes control by calling Release. If the resource is pooled, each call to Acquire and Release respectively decrements and increments a counter of available resources. More complex interactions between resource users and resources than Acquire - Release are also possible as in the home-heating system in section 3.1.7.

3.1.6 Consequences With public exclusive access, the resource-user thread is responsible for releasing the resource.

3.1.7 Implementation and examples In the home-heating system [6, 20, 23], the heater in each of a number of homes is controlled by a sleeper thread that waits while the fan motor revs up, while the system cools down before being restarted, etc. Multiple homes share a fuel tank, and only, say, 4 out of 5 homes can be heated at any one time. A synchronized object contains 4 heating tokens that are handed to calling home threads, which are also resource users. No home can be without heating for more than 10 minutes, so the object has an Insist operation in addition to Acquire and Release. Furthermore, Acquire has no synchronization condition but returns a negative status when no tokens are available. That way, a home thread can call Acquire repeatedly for 10 minutes, then call Insist.

3.1.8 Related patterns In a sense, Assembly Line is the dual of Shared Resource. In Assembly Line, the resources are the threads. (Both the resource user and the resource cannot be threads, since this would violate the rule that each event is handled by one thread.)

11

Ui may acquire exclusive access to Rj then proceed to acquire Rk before releasing Rj. This is Simultaneous Exclusive Access. (See section 3.4.)

3.2 State Machine 3.2.1 Motivation The purpose of State Machine is to capture the groupings of active and passive objects used to implement a state machine in different cases. The purpose is not to capture the internals of a state machine. We shall assumed that it is represented internally in traditional ways: In a synchronized object, there is a state variable, usually of an enumerated type, which typically appears in wait conditions. In a thread, the state can also be represented implicitly by the value of the program counter.

3.2.2 Applicability Simple but non-trivial applications of state machines are: an automated gate or garage door, an answering machine, a window elevator for a car and a cruise control system for a car.

3.2.3 Structure

E

F

C

P

3.2.4 Participants E: Event captor. F: Finite automaton. C: One or more activities. An activity can be either a series of a periodic actions or a lengthy computation. P: One or more passive interfaces.

12

3.2.5 Collaborations E captures events, which are sent to F by means of calls from E to F. F determines any state transition and/or actions in response to each event. It also produces any timing events. C performs any activity relevant to the present state. P represents any passive interfaces called from F and/or C.

3.2.7 Implementation Event captor: If the events are detected by means of polling, E is one or more sampler threads. If events emanate from other subsystems, then E represents those subsystems. If the events are interrupts, E is the system software that links a hardware interrupt to an interrupt handler. Sampling and interrupt handling may co-exist in one system. Finite automaton: F is the state machine itself. It is either a synchronized object or a thread. It is a thread if there are timing events, which are implemented by means of sleep statements. F may also be a thread that incorporates certain activities; see below. If there is no compelling reason for F to be a thread, it is implemented as a synchronized object.

Activities: An activity, C, that consists of a series of actions can be represented in one of two ways. 1. F and C are put together in one thread. This simple solution applies if each activity is limited to single states. An activity that starts in one state and continues in another must be in a separate thread, as described next. 2. C is one or more periodic threads, separate from F. In the absence of timing events, F becomes a synchronized object. F provides a means for C to query the current state. To ensure that activities are turned on and off properly, it is critical that C query the state at appropriate points in its cycle, typically upon return from its suspension.

Passive interface: P is one or more objects referenced by F and/or C. P may provide external interfaces and/or hide data structures. P is passive in the sense that F and C query it to obtain the values; P does not call F or C. Although P may contain a thread, this is invisible to F and C.

3.2.8 Examples The following examples are fully discussed in [21].

13

The odometer problem; F and C combined in a thread. The bicycle odometer in this example has 4 states: Distance, Speed, Mileage and Time. In each state, it repeatedly displays one quantity: distance traveled, speed, total mileage or elapsed time, with a different frequency for each quantity [19]. Two buttons, A and B, are used that generate interrupts. By pressing A, the biker changes the odometer's state cyclicly from Distance to Speed, etc., back to Distance. When B is pressed in the Distance and Time states it resets a reference distance and a reference time, respectively. B has no effect in the other states. The state machine and the activities are combined in a thread, Odometer. Odometer calls Wheel, which is a passive interface (P) that receives an interrupt every time the wheel has completed a revolution. The synchronized operations Wheel.Dist and Wheel.Speed return the current distance and speed, respectively. Automobile cruise control problem; separate activity thread. The cruise control problem is a popular example of real-time software design [5, 6, 8, 12, 18, 19, 21, 24]. The car driver determines the state of cruising by manipulating a cruise control switch, the brake and the gear stick, which cause interrupts. F is the synchronized object Driver. C is the periodic thread Throttle. The throttle-control activity is executed in different states: when cruise control is on, and when the car is made to accelerate at a fixed rate. Throttle includes the control law that determines the pull on the accelerator wire. Throttle senses the current state of cruising by calling a synchronized operation Driver.State, and gets the target speed from Driver.Speed. Shaft is a passive interface (P). The operation Shaft.Speed returns the current speed. Alternatively, the throttle-control activity could be combined with the finite-state-machine representation in a Driver thread.

3.2.9 Consequences Whenever possible, a synchronized object represents the state machine, F. This implementation affects the changeability of the design. If F is first implemented as a synchronized object and the need for a timing event arises later, F must be re-implemented as a thread. One solution to this problem is to implement F as a thread always, but this complicates the solution in the simple case with an additional thread. In a solution that goes against the pattern, F remains a synchronized object, and timing events are implemented by means of separate timer threads. (See 4.1.)

14

3.2.10 Related patterns The internal representation of a state machine is given as the State pattern in [3]. State relies on polymorphism and dynamic binding to avoid explicit branching based on state and event combinations. Shared Resource: When multiple activity threads access the state machine, F, with exclusive access they are resource-user threads.

3.3. Assembly Line 3.3.1 Motivation Resource sharing is a central issue in any non-trivial concurrent system.

3.3.2 Applicability The assembly-line pattern is applicable in problems where parts (bags, railway cars, song requests, customers, etc.) flow between handling stations of some kind. Each station can handle one part at a time. Each part needs exclusive access to one station at a time. In some systems, there is a physical assembly line of some sort. Mutual exclusion and queuing of parts is then typically ensured by the physical arrangement of the assembly line. Examples of this nature include an airport baggage handling system and control systems for a railway and for various assembly plants. In the jukebox, the selection and player stations are physical but the song requests are kept as data. In other examples, stations are data conversions implemented in software. In these cases, the software must ensure that the order of the parts is not changed unintentionally and must provide for the necessary queuing.

The buffering of parts between stations makes a difference in the design. With no buffering, each station hands over one part at a time to the next station. The handing over proceeds from the end of the line to the beginning. Each station can operate concurrently on its part, but must await the slowest station downstream before handing over the part to the next station in line. Adding buffers allows for variable handling times at each station.

In another case, there is more than one server per station. This allows multiple parts to be serviced in parallel. Each server then has a thread.

15

3.3.3 Structure

Sn:Station

Put

Get Qm:Queue

Sn+1:Station

The assembly line may contain forks and joins: A station may put different kinds of parts in different queues, more than one station may put parts in a given queue, more than one station may fetch parts from a given queue, and a station may consist of multiple servers, operating concurrently.

3.3.4 Participants Sn - Sn+1: Stations Qm: Queue

3.3.5 Collaborations Each station, S, gets the next part from a queue Qi, processes it and puts the processed part in another queue, Qj. The first station in an assembly line obtains parts in a different manner, and the last station disposes of them in a different manner. If there is no queuing, the Q objects may be mailboxes with room for a single part, or the stations may communicate directly.

3.3.6 Consequences Except when the assembly line branches or when a station has multiple servers, the pattern preserves the order of the parts that travel through the line. A part gains exclusive access to one station at a time. As a rule, a part should have exclusive access to no other resource. If a part leaves a station with some resource allocated to it, this creates unwanted coupling between the station threads: One thread allocates the resource to the part, and another one must ensure that it is released. Additional exclusive resources can be obtained during the processing of a part by each station but should be released before the part leaves the station. (In the baggage handling system in section 3.3.7, for example, various threads update shared database records.) Another exception may be a resource that is allocated to the part at the outset

16

and kept till the end. In the baggage system, each piece of baggage has exclusive access to a cart.

The queues are typically implemented as dynamic data structures. Care must be taken to avoid overflow in case the station that empties the queue stops.

An assembly-line implementation may include stations without resources to accommodate computation that requires no resource.

3.3.7 Implementation and examples Line workstations: In an assembly line [5], workstations are placed along a conveyor belt. Each workstation has a tool that operates on one part at a time, and a robot that moves a part from the conveyor into the tool and vice versa. Each workstation requests a part from its predecessor on the assembly line when it is ready for a new part. The predecessor answers the request with information about the new part when it has been sent. Each workstation goes through a sequence of steps for each part starting with the part request and ending with the placing of the part.

Baggage handling system: In a baggage control system for an airport, the stations are waypoints where a piece of luggage may be routed off the main conveyor onto a baggage pick-up or loading area, or vice versa [19].

Remote temperature sensor (RTS) [15, 17, 18, 26] (An assembly-line solution appears in [19].) The RTS is a microprocessor connected via a multiplexed A/D converter to thermocouples that sense the temperature of each of a number of furnaces. Each furnace is monitored at its own variable rate. RTS sends each temperature reading to a host computer via a communication line. There are two stations - threads: Temp_Sampler, corresponding to the A/D converter, and Output, corresponding to the data line to the host. The stations are connected by a queue of unsent readings. Readings may accumulate if the host connection is broken. To avoid queue overflow, Output deletes old readings when necessary.

MIDI Patch Bay [23]: A different version of the assembly-line pattern is when the software receives a stream of messages and sends a modified stream of messages. The software may

17

merge multiple input streams into one output stream and/or split the input stream into different output streams, etc. Order preservation is essential: The messages may be intentionally reordered, but not arbitrarily. Typically, throughput is a primary concern; the software must keep up with the rate of incoming messages. (The response time between message input and message output may be a lesser concern as long as it is consistent.) If several conversion steps and several processors are available, it is desirable to set up a virtual assembly line of processors - stations.

A programmable patch bay has a number of MIDI ports, each of which can be configured for input or output. In addition to forwarding messages to ports configured for output, each input port can also to translate messages by replacing their status bytes, suppress messages with certain status bytes or place higher-priority messages before lower-priority messages in the output. The patch bay has one thread per station. Synchronized objects are used to handle the input and output devices.

3.3.9 Known uses The assembly line pattern is common in the literature as illustrated by the cited reference [5]. The Active Object System (AOS) [13] simulation environment is also essentially based on assembly lines.

3.3.10 Related patterns State Machine: Each station can often be represented by a state machine. The queues are passive interfaces.

Shared Resource: A shared-resource solution is generally an alternative to an assembly line. Assembly Line tends to yield the simplest solutions when the parts do little but wait for and use shared resources. Shared Resource should be considered if much computation relies on no shared resource or if there is complex simultaneous exclusive access.

Transform (rejected; 4.2): A "faux" assembly lines has queues where there is nothing to wait for. For a queue between a producer and a consumer station to be justified, the stations must be on different schedules so that wait is possible, as in the following cases:

18

- The consumer has lower priority than the producer. (In this case, the queuing is for the processor.) - Each station has its own processor, and the processing time at each station is variable - The consumer is on a set schedule

3.4. Simultaneous Exclusive Access Shared Resource extends naturally to the case where a resource user has simultaneous exclusive access to multiple resources. Although the pattern remains the same, this raises the important issue of deadlock prevention as discussed at length in [18, 19]. Identifying resourceuser threads facilitates deadlock prevention. Various thread models of a flexible manufacturing system (FMS) are discussed and compared in [22]. The FMS is primarily concerned with resource allocation. Another example is an automated train switchyard [18]. Related pattern: Monroe's Shared Resource architectural pattern [14].

4. Rejected patterns. 4.1 Timer Explicit timers are as undesirable as goto statements and semaphores, and offer similar problems: It becomes the programmer's responsibility to cancel a timer that is no longer needed, as when an event subject to time-out actually occurs.

4.2 Transform Transform is a common pattern promoted by design approaches for real-time software. Like Assembly Line, it consists of threads connected by a flow of data with or without queuing. Unlike a station thread in the Assembly Line, a transform does not have to be associated with a resource. Although Transform sometimes results in similar solutions as State Machine or Assembly Line, the pattern is too open-ended. There is no guarantee that the transform threads will ever operate concurrently; instead, they may execute one after the other. Although there may be no requirement of preserved order, Transform may force a pipe line structure on computations that might just as well be executed concurrently by different threads. That way, the computations themselves unnecessarily become shared resources. Transform may also violate the rule that each event should be handled by a single thread.

19

5. Conclusions The examples suggest that it is possible to express most multi-threaded solutions in a small number of patterns. By following a pattern language consisting of a number of well-known patterns, the designer of multi-threaded software can enforce a consistent architecture. This helps those who have to deal with the software after the developer, such as maintainers.

References [1] M. Awad, J. Kuusela, and J. Ziegler. Object-oriented technology for real-time systems, Prentice-Hall 1996 [2]. R. Bræk and Ø. Haugen. Engineering Real-time Systems. Prentice Hall Europe 1993 [3]. E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable ObjectOriented Software, Addison-Wesley 1995 [4] J. Gil and D. H. Lorenz. Design patterns and language design. IEEE Computer, 31:3 (March 1998), 118-120 [5] H. Gomaa. Software Design Methods for Concurrent and Real-time Systems. Addison-Wesley, 1993. [6] D. Hatley and I. A. Pirbhai. Strategies for Real-Time System Specification. Dorset House, 1987. [7]. I. Jacobson. Object-oriented Software Engineering, Addison-Wesley, Reading, MA, 1992. [8] D.-W. Jones. Cruising with Ada, Embedded Systems Programming 7:11 (Nov. 1994), 18-44 [9] N. L. Kerth and W. Cunningham. Using patterns to improve our architectural vision, IEEE Software, January 1997, 53-59 [10] R. G. Lavender and D. C. Schmidt. Active object: An object behavioral pattern for concurrent prond gramming, Proc. 2 Annual Conference on the Pattern Languages of Programs, Monticello, IL, Sept. 1995 [11] D. Lea. Concurrent programming in Java™. Addison-Wesley, 1997 [12] S. J. Mellor and P. T. Ward. Structured Development for Real-Time Software. Vol. III Implementation Modeling Techniques. Yourdon Press, 1986. [13] T. Minoura, S. S. Pargaonkar, K. Rehfuss. Structural active object systems for simulation, Proc. OOPSLA 1993, 338-355 [14] R. T. Monroe, A. Kompanek, R. Melton and D. Garlan. Architectural styles, design patterns and objects, IEEE Software, January 1997, 43-52 [15] K. W. Nielsen and K. Shumate. Designing large real-time systems with Ada. Communications of the ACM, 30:8 (August 1987) 695-715. Corrected in CACM 30:12 (December 1987) 1073 [17] B. I. Sandén. Entity-life modeling and structured analysis in real-time software design - a comparison. Communications of the ACM, 32:12 (December 1989) 1458-1466.

20

[18] B. I. Sandén. Software Systems Construction with Examples in Ada. Prentice-Hall, 1994. [19] B. I. Sandén. A course in real-time software design based on Ada 95, Available through the ASSET repository as ASSET_A_825, 1996. http://www.asset.com/WSRD/abstracts/ABSTRACT_825.html [20] B. I. Sandén, Using tasks to capture problem concurrency. Ada User Journal, 17, 1 (March 1996), 25-36. [21] B. I. Sandén. The State Machine pattern. Proc. TRI-Ada, Philadelphia, PA, Dec. 1996, 135 - 142. [22] B. I. Sandén. Modeling concurrent software. IEEE Software, September 1997, 93-100. [23] B. I. Sandén, Concurrent design patterns for resource sharing. Proc. TRI-Ada, St. Louis, MO, Nov. 1997, 173-183. [24] M. Shaw. Comparing architectural design styles, IEEE Software, November 1995, 27-41 [25] F. J. van der Linden and J. K. Müller. Creating architectures with building blocks. IEEE Software, November 1995, 51-60 [26] S. J. Young. Real-time Languages: Design and Development, Ellis Horwood, Chichester, West Sussex, England 1982

language 1998-07-04

21