Printer Friendly

Concurrency annotations for reusable software.

It is intuitive, has been proved by real systems, and researchers agree on it: concurrency blends well with the object paradigm. The evidence, however, comes mainly from systems that are object-based, not object-oriented: inheritance is rarely involved. Focusing on programming language design, the very notion of concurrent object-oriented programming calls for a smooth integration of concurrency with inheritance.

If we are to measure a concurrent object-oriented language (henceforth abbreviated COOL) against the yardstick of concurrency/inheritance integration, we have to distinguish different degrees of integration:

1. No integration: The object-oriented features and the concurrency features of the language are independent of each other. Although such a language is both object-oriented and concurrent, it cannot be considered an actual COOL. Example: Concurrent C++ [9].

2. Partial integration: Concurrency is integrated into the object-based sub-language, giving rise to phenomena such as synchronized objects, active objects, and asynchronous object invocation. Inheritance, however, does not apply to active and/or asynchronous classes. Such a language is not the real thing either; it could be termed "concurrent object-based with inheritance." Example: Guide [12]; only passive classes are supported in Guide.

3. Full integration: A real COOL supports inheritance hierarchies that include active classes. Example: Pool-I [4]; each class is considered active in Pool-I.

Even with full integration, the language designer has considerable freedom concerning the details of the concurrency/inheritance interaction. The subject is still not well understood. The COOLs available today suffer from the so-called inheritance anomaly [15]. It has even been doubted whether concurrency can blend well with inheritance [3]. One purpose of this article is to contribute to a better understanding of the relationship between concurrency and inheritance.

The acclaimed benefit of inheritance is code reuse. Reusability is an important factor in the development of software, both sequential and concurrent. It has several facets, however, and inheritance is only one of them. It is important for practical software engineering that both sequential and concurrent programs can be developed within the same language framework, and that the boundary between sequentiality and concurrency be no impediment to reuse. This calls for a COOL approach that minimizes the differences between syntax and semantics of sequential versus concurrent code.

Ideally, any piece of code should have both a sequential and a concurrent interpretation, and they should be "reasonably close." Approximation of this ideal is another goal of the COOL approach presented here.

Inheritance vs. Subtyping

Inheritance must not be confused with subtyping and polymorphism. While inheritance furthers code reuse, subtyping allows for flexible static type systems, helps with conceptualization and design and supports design reuse. Few languages support a clean separation of a program's subtyping hierarchy. Prominent examples among the COOLs are Pool-I and Guide.

It is an open question whether or not concurrency issues should be reflected in the type system: the answer depends on whether concurrency properties are already captured by the signature of a class or only by its semantics as expressed by a complete formal specification. Our answer will be a hesitant "Yes": the type of a concurrent class will be introduced as a subtype of a corresponding sequential class with the same signature.

Concurrency Annotations and CEiffel

There has been a wealth of research into concurrent object-oriented programming in the last few years. See [1, 2, 20, 21, 23] for an overview of work in this area.

The approach to concurrent object-oriented programming presented here is based on extending a sequential object-oriented language with concurrency annotations, leading to a COOL with good reusability properties. The presentation vehicle is Eiffel. Version 3 of the language [19] is extended toward a language called CEiffel (C for "concurrent"). Eiffel was chosen because the annotations approach fits in naturally with certain features of the language. The reader should be familiar with the basic concepts and terminology of Eiffel as introduced in [18].

The approach is not strictly tied to Eiffel. There are also one or two fine points for which other languages might be more helpful than Eiffel (see the subsection "Assessment" near the end of this article). It should be noted, however, that Eiffel's type system, although somewhat controversial, is readily compatible with the proposed annotations.

Eiffel has been used as the basis for concurrent programming before. Eiffel// [6, 7] is a slightly extended version 2 of Eiffel with special library support. Distributed Eiffel [11] is a considerably modified version with a distributed implementation on top of the Clouds operating system. Other approaches to concurrency in Eiffel are described or referred to in other contributions to this issue of Communications.

Concurrency annotations were first introduced in [13] and further developed in [14]. The annotations are inserted into otherwise sequential code. They come in the guise of Eiffel comments; thus, a CEiffel class would be accepted by a compiler for (sequential) Eiffel. Using comments rather than keywords is, of course, not essential. The important point is that a CEiffel class has both a sequential and a concurrent meaning. Choosing comments is a pragmatic decision that allows use of existing Eiffel compilers for sequential interpretation.

Our concurrency annotations start and end with --, as in the "control annotation" --!-- to be introduced. This is not standard Eiffel (where a comment ends with the line end), but allows for a better program layout in some cases. Lest the reader be misled, annotations are not just pragmas or hints for the compiler (e.g., for automatic parallelization of a sequential program). They do modify the semantics of the program, if only in a very moderate manner.

Terminology

Operations of a class include exported routines and read operations on exported attributes. The invocation of an operation for an object (as opposed to a local routine call) generates a request. A request may remain pending for some time before it is accepted by the object. Acceptance causes the activation of the operation, which is called an activity. An activity is suspended while it is waiting for the termination of an activity it has generated.

Concurrency Control

Correctness of an implementation always refers to a given specification. Imagine a model-based specification for a class, with an abstract state and pre- and postconditions for the operations. The model can be implemented not only as a sequential class, but also as a concurrent and/or shared class: internal concurrency is an implementation detail; "shared" means that objects of that class can be shared among concurrent activities.

The atomicity property of the specification, implied by the timelessness of the state transitions, is trivially observed by sequential implementations. It must also be observed by a shared implementation. An atomic implementation, as in monitors, serves this purpose. But an implementation need not be atomic in order to be correct. Concurrent activities on an object can be allowed if any execution is serializable as known from transactions in database systems: the combined effect of a concurrent execution of activities must be identical to the combined effect of some serial execution.

In many cases not even serializability is required. There are also cases in which serializability is not even desired. In general, not all desirable behaviors of shared objects can be expressed by means of atomic specifications. So the programming language, although it must support atomicity, should also allow for more liberal concurrency control.

The Control Annotation

The control annotation --!-- governs the interpretation of CEiffel code. It is attached to the declared type of an entity, as in q: Queue [Message] --!--; register (name: String): Group --!-- is...

If A is a type, A--!--is a subtype of A with an interface identical to that of A. Any concurrency annotations in the class text of A will be ignored for an A object, but will be obeyed for an A--!-- object, also known as a controlled A object. Thus, whether the sequential or the concurrent semantics of a class applies to its objects is decided on a per-object basis. The decision is up to the programmer.

If the control annotation is obeyed in an object (together with the other annotations), it must have been obeyed in the creator as well. By induction, it must have been obeyed in the root object. So we can say that a concurrent program is started by creating a controlled root object. Note that polymorphism allows an entity of type A to refer both to controlled and to noncontrolled A objects at run time.

Compatibility Annotations

As previously explained, the natural concurrent semantics of a sequential class relies on atomicity; thus a sequential class will also be called atomic henceforth. The CEiffel compiler automatically provides an atomic implementation for controlled objects of an atomic class.

A closer look at a class often reveals that certain operations are compatible with others: strict atomicity can be relaxed in a way that activities for compatible operations may overlap. In the extreme case, arbitrary overlappings of operations are allowed. This is indicated by attaching a compatibility annotation --||-- to the class head, as in

class Package --||--

The class is then called a concurrent class.

Semiconcurrent classes are neither atomic nor concurrent. A semiconcurrent class is obtained by attaching compatibility annotations to routine heads. These annotations have the form

--|| Operation[underscore]List --

where Operation[underscore]List is a sequence of routine or attribute names, separated by commas. The routine head

rO (...): T is --|| r1, r2, ... --

expresses that rO is compatible with r1, r2, ... Compatibility is a symmetric relation; the programmer can omit redundant compatibility information in the annotations. Note that compatibility need not be transitive or reflexive.

A routine annotated with --||-- is compatible with itself and with all other routines annotated in this way. --||-- is typically used for read-only operations. Access to an attribute is considered to be compatible with everything; so the programmer has to be cautious when exporting attributes. The compatibility annotations are effective only for invoked operations of controlled objects; they are ignored in local calls. A locking mechanism guarantees the mutual exclusion of incompatible operations. When a request arriving at an object is incompatible with ongoing activities, it remains pending until all incompatible activities have terminated. When an activity terminates, several pending requests may become acceptable; they are accepted in the order of their arrival.

Compatibility annotations can be viewed as a generalization of the access/modifies keywords for read/write locking is Distributed Eiffel. Read/write locking is specified in CEiffel as shown in Figure 1. The standard handling of pending requests as described in Figure 1 may produce the well-known scheduling anomaly that allows readers to starve writers. This can be prevented by implementing an explicit scheduler or, more comfortably, by using scheduling predicates [13, 16].

If a highly concurrent implemetation for a given specification is requested, we can often improve on the simple read/write locking. The programmer has to find a data representation that allows as many compatibilities as possible. The class Queue shown in Figure 2 represents a well-known ring buffer implementation with decent compatibility properties. Note that violated preconditions in enqueue and dequeue may raise exceptions in cases where blocking would be appropriate--we will return to this later.

The careful reader may wonder whether the attributes in the class Queue should be controlled, as they are definitely shared among concurrent activities. But no control annotations need to be attached to shared integers, characters, and so forth. The basic classes all have atomic implementations (indivisible read/write operations in hardware). Reading and writing references is also atomic. The library classes Array [T] and String are concurrent in the present version of the CEiffel system, but without a well-defined concurrent semantics. The classes do behave atomically if only read operations and--for arrays--put operations for basic and reference types T are executed.

Compatibility Inheritance

The compatibility annotation of a routine may refer to an operation in an ancestor. The possibility to omit the symmetric compatibility is vital here: we do not want to redefine an inherited operation only for the sake of adding an operation name to its annotation. It is, of course, always possible to modify the inherited compatibility relation in the wake of redefinition. If a feature is redefined, all its (new or old) compatibilities must be listed in the annotation (i.e., there is no automatic inference of symmetric compatibilities). To see why, consider the class fragment

... x is --|| y -- ... y is .....

and an heir of this class where y is redefined and is no longer compatible with x. We must be able to express this in the heir, most naturally by

... y is .....

If, however, y is still compatible with x, this is stated explicity:

... y is --|| x --

Deferred routines must not be annotated. It should also be noted that there is no sound basis for attaching a compatibility annotation to a routine that directly or indirectly calls a deferred routine. The conservative approach then is to let that routine be incompatible with anything. If a compatibility annotation is attached to the routine, this obliges the implementer of the deferred routine (who might not be able to fulfill the obligation!) to supply a compatible implementation.

In the case of multiple inheritance, two routines inherited from unrelated ancestors B and C are compatible if no repeated inheritance is involved (i.e., if B and C have no common ancestor A); they are incompatible otherwise. Two routines inherited from one ancestor through repeated inheritance keep their original compatibilities.

No user-defined class starts out as an unknown quantity. It implicitly inherits from the standard class Any. Polymorphic routines for comparing, copying and cloning objects are available through Any. The original Any is atomic, but the user can of course substitute a nonatomic version. Unfortunately, because of the design of the routines offered by Any, it is not possible that reading of an object is subject to concurrency control. This is because objects to be read for comparing, copying and cloning are passed to the respective routines as arguments, as in clone (x). If an object could be cloned by x.clone instead, the annotation --||-- could be employed. This would also pave the way for safely comparing and copying objects, using non-shared clones.

There are two ways to deal with the problem. Either the CEiffel semantics for Any is defined in such a way that reading an entire object is performed as a routine annotated with --||--; or reading is not subject to any locking at all. The present version of the CEiffel system, relying on a precompiler from CEiffel to Eiffel, had to chose the latter alternative.

Delayed Acceptance

The Queue implementation given in Figure 2 satisfies the specification written in a specification language reminiscent of Eiffel, as shown in Figure 3. Note that the pre/postconditions do not refer to a concrete data representation as in Eiffel, but to an abstract model of a queue, built from components size and seq. The specification relies on a specification Sequence with functions append (at rear), insert (at front) and length. Sequence captures the behavior of sequences of unlimited length that are initially empty. The abstract state of Queue includes the components size and seq.

The preconditions characterize the domains of the abstract operations. The specification is not concerned with the behavior of an implementation outside those domains (i.e., when the abstract precondition is violated in a given concrete state). There are several possible behaviors, two of which are of interest to us. They have in common that a concrete precondition is used which is satisfied if the abstract precondition is satisfied:

* Noncontrolled object: A violated precondition raises an exception (if the corresponding compilation switch is on).

* Controlled object: A checker and a guard part are distinguished in the precondition. A violated checker raises an exception, rejecting the request; a violated guard causes a delay: the request remains pending and will be accepted later, when the guard is satisfied (and no incompatible activity is present).

The checker and the guard are separated by the delay annotation --@-- which is inserted in front of one of the assertion clauses of the precondition. The routine is then said to be delayed, and so are the class and its controlled (!) objects.

A fragment of an atomic delayed class Queue with the obvious delays for enqueue and dequeue reads as follows:

enqueue (item: T) is require item /= Void; --@-- length < size do ......... end;

dequeue: T is require --@-- length > 0 do ......... end;

Again, the important point with delayed classes is the proximity of the sequential and the concurrent semantics of this code: what causes a delay in a concurrent setting raises an exception in a sequential setting. The abstract specification is identical in both cases.

The detailed handling of a request that is not rejected right away is as follows: If the compatibilities permit, an activity is tentatively started for evaluating the guard; if not, the request remains pending. If the guard is satisfied, the activity continues (accptance). If not, the activity is aborted and the request remains pending (delay). Whenever an activity terminates, this procedure is repeated for all pending requests in the order of their arrival. Note that this strategy may not fully exploit the available concurrency in nonatomic objects; it is a pragmatic solution that limits the amount of reevaluation of guards.

The delay annotation can also be used in postconditions. This may seem strange at first, but sometimes an activity needs help from another activity to achieve its goal, as stated in the postcondition. Then the option of waiting for the "completion" of the postcondition comes in handy. The concept is important for implementing schedulers. A very simple example is a rendezvous object for the synchronization of several activities: the invocation r.await(n) delays the caller until n-l other activities have called await. The implementation is shown in Figure 4.

Guards blend well with inheritance. Redefinition of a guard is rarely required. This contrasts favorably with other COOLs where a central agency such as an imperative body or a declarative control section is responsible for controlling delays, exclusion and explicit scheduling. An important asset of CEiffel, responsible for the smooth integration of concurrency and inheritance, is a strict separation of concerns: delays have their roots in specification, and are therefore treated separately from compatibilities, which are governed by implementation considerations. Scheduling is an additional issue which must not be mixed up with the others.

Exploring the ramifications of weakening/strengthening pre/post-conditions with guards when routines are redefined is left to the reader. Recall that when a routine is redefined its precondition can only be weakened and its postcondition can only be strengthened. Considering guards, we see that a subclass may feature less preactivity delays and more postactivity delays than its superclass. This has to be taken into account when designing class libraries. More on the subject can be found in [13].

Concurrency Generation

The generation of concurrency is bound to routines in CEiffel. The extended syntax for a CEiffel routine is

CRoutine = [Autonomy[underscore]annotation | Asynchrony[underscore]annotation] Routine

Autonomy[underscore]annotiation = -->-- Asynchrony[underscore]annotation = --v--

Either annotation may be inserted between the keyword is and the routine proper in a routine declaration. These annotations are responsible for the generation of concurrent activities as described in the following subsections.

Autonomous Objects

A routine annotated with -->-- is called an autonomous routine. Its signature and its checker must be empty. A class featuring one or more autonomous routines is also called autonomous, and so are its objects. When an autonomous object has been created and initialized, all its autonomous routines are invoked implicitly (i.e., without explicit invocation by some activity). When an autonomous routine terminates, another implicit invocation of that routine is issued automatically. Autonomous routines are usually not exported, although one may do so. Explicit invocation of an exported autonomous routine is treated like any other invocation.

Guards can be used to control the execution of autonomous routines; checkers are not allowed. An unreachable autonomous object will be garbage-collected as soon as there are no activities on the object and no acceptable requests. A

very simple example of an autonomous class is presented in Figure 5. In Figure 5, tick is part of the internal "clockwork" and therefore is not exported. Note that nothing is said about real time here. There are obvious enhancements for aligning the activities of objects with real time, but they are beyond the scope of this article. Because the class is atomic it is not necessary to worry about interleaving executions of, say, tick and set in Figure 5, even if operations on integers were not atomic. A specification for Clock can be given as shown in Figure 6.

In Figure 6 the abstract state has just one component--the natural number time. The relationship between abstract and concrete state is trivial in this example: the abstraction function is just the restriction of the integers to the naturals. Two state transitions are specified in Figure 6--set and tick. It is important that we can treat both set and tick on the same footing at this level of abstraction. Establishing the correctness of a class with respect to its specification is independent of exports and autonomy. This is in contrast to other approaches where the behavior of an active object is described by a sequential "body" in the class (not unlike the body of an Ada task). Note that although tick is not exported, -->-- is not just an implementation detail. It is part of the specification--not of its signature, but of its semantics.

Autonomy and Inheritance

No complicated rules are required for inheritance hierarchies containing autonomous classes. An autonomous routine is inherited just like any other routine. Feature adaptation may cause a change in the invocation status of a routine: an autonomous routine may be redeclared as nonautonomous and vice versa. For this reason, an autonomy annotation in a deferred routine declaration is considered merely declamatory--it does not bind the effective version of the routine. With multiple inheritance, no trouble arises from joining routines with different autonomy status--the final status will be that of the inherited effective routine, if any. As a simple example, let us extend the Clock to obtain an AlarmClock. Notice the use of a guard for controlling the alarm as shown in Figure 7. As shown in Figure 7, an AlarmClock object has two autonomous routines, tick and ring. A discussion on how this might be implemented is postponed until later. Suffice it to say that only the most naive implementation would come up with two threads per AlarmClock object.

Asynchronous Objects

Asynchronous invocation of an object is supported by the asynchrony annotation --v--. An exported routine annotated with --v-- is called an asynchronous routine. A routine that is neither autonomous nor asynchronous is called synchronous. A class featuring one or more asynchronous routines is also called asynchronous, and so are its objects. A class/object can be both autonomous and asynchronous.

When a client has invoked an asynchronous routine of a server, it proceeds right after evaluation of the checker and before the server has started working on the request. Thus, asynchrony is the second source of concurrency in a system, in addition to autonomy. Given the "vertical" nature of invocation in a functional hierarchy, asynchrony is sometimes referred to as vertical concurrency, as opposed to the horizontal concurrency caused by autonomy. The v in the asynchrony annotation can thus be read either as a downward arrow or as a shorthand for "vertical"--The asynchrony annotation is ignored in local calls.

If a synchronous invocation x.op1 is followed by an invocation x.op2, the activity generated for op1 precedes that for op2. This seems too trivial to be mentioned--but: it is not guaranteed if op1 is asynchronous. It is not even guaranteed that the requests arrive at x in the order of their generation (so that at least objects with strict FCFS acceptance would preserve the invocation order). The reason for not giving stronger guarantees in CEiffel is that simple distributed implementations should not be precluded. If order preservation is a concern, op1 must either be synchronous or else must be a function as will be explained.

Asynchrony is not confined to procedures (i.e., routines without a result). The invocation of a function also immediately returns, delivering a proxy for the result. Lazy synchronization is used in claiming the result: only actual access to the result is synchronized with the termination of the corresponding activity. This technique is due to [6]. It is an improvement on earlier devices such as "futures" [22] and was introduced for Eiffel// as "wait-by-necessity."

Again, a very simple example should suffice to demonstrate asynchrony (see Figure 8). The example in Figure 8 is a Printer class. It offers a print operation for printing a file on a printer associated with a Printer object. The operation kill stops and cancels the current printing job, if any. It is important that kill be declared compatible with print, not because these operations do not interfere but because kill must interfere with print. Whether or not kill is declared compatible with itself does not really matter. Notice that print is not compatible with itself. print activities are serialized. This provides a basic spooling functionality and obviates the need for an explicit spooler.

Similar to autonomy, asynchrony does not pose any problems in connection with inheritance. Its treatment is analogous to that of autonomy. This section concludes with an example in which an AlarmedPrinter class inherits from Printer and uses a class PrinterAlarm which inherits from AlarmClock (see Figure 9).

Conclusion

Concurrency annotations in CEiffel code can be either ignored or obeyed, giving rise to a sequential or concurrent semantics. Several simple examples have been given to support the claim that these semantics can often be very close.

A crucial role is played by the

control annotation --!--

which affects the creation of an object. It is responsible for making the annotations, including itself, effective in the new object.

Concurrent activities owe their existence to either of two annotations which are attached to routine heads:

autonomy annotation -->-- asynchrony annotation --v--

Invocations of asynchronous functions use lazy synchronization for claiming the result. If the annotations are ignored, we just have regular synchronous routines.

Delaying an activity for reasons apparent from the specification is achieved using the

delay annotation --@--

This annotation splits the precondition (and similarly the postcondition) in two parts, the checker and the guard. A violated checker raises an exception, a violated guard causes a delay. Ignoring the annotations turns delays into exceptions.

Finally, the class text chosen as the implementation of a specification determines the mutual exclusion required between activities. Continuing with the declarative style of the other annotations,

compatibility annotations --| |-- or --| | ... --

are attached to class or routine heads. The default is therefore "no compatibilities," consistent with the fact that a nonannotated, sequential class is trivially atomic. Thus, the control annotation produces atomic objects from regular Eiffel classes.

Assessment

Concurrency annotations are attractive because of their lightweight character and their orthogonal design. They are easily inserted into, removed from, or ignored in CEiffel code. Changing the invocation status of a routine does not affect its delays and compatibilities. Autonomy is not tied to atomicity. Delays and compatibilities are independent concerns.

Perhaps the most important difference between CEiffel and other COOLs lies in the locality of the annotations. This is a debatable issue. It has often been argued that synchronization properties of a program component should be stated separately from the code proper, and preferably be gathered in a central place. Path expressions are an early example [5], and Guide's control section is a typical example in the context of an object-oriented language. The declarative style of the control sections is certainly laudable, but they fall short of compatibility with inheritance: Redefinitions become the rule rather than the exception, and these can become very cumbersome because delays, compatibilities and scheduling are indiscriminately mixed up in the control section.

In contrast, CEiffel keeps the declarative approach but attaches annotations to individual routines. Automatic inference of symmetric compatibilities further improves locality. All this naturally goes with inheritance: as long as code is not redefined, delay annotations and compatibility annotations need not be redefined either. So it turns out that the often-deplored inheritance anomaly [15] vanishes when using proper high-level language features. A similar case in the context of rewriting logic is argued in [17].

The alternative to autonomous routines is the Ada-like approach in which a class body describes the life-long behavior of an active object. The proponents of this approach can argue that sequential control is a simple and powerful device; without it, the missing program counter must be encoded in the object state somehow. A standard example for the benefits of sequential control is post-processing: when a client has been allowed to continue after a synchronous invocation the server continues with clerical work necessitated by that invocation. Implementing this is not a problem with Ada (or with Pool or several other object-oriented languages). In CEiffel, however, a separate autonomous routine has to do the postprocessing, and it must be controlled by a special guard.

But what seems to be a weak point again turns out to be an asset in the light of inheritance. Inheriting autonomous behavior (or multiple behaviors), as well as extending or restricting it, is trivial. The same is true for asynchrony. There is no central class body that must be modified and there is no need to modify the bodies of the participating routines.

Implementation

The notion of activity has been ubiquitous in our discussion of the annotations. The "average" activity is a very lightweight kind of a process, definitely lighter than a system-level process, often even lighter than a user-level thread. Let us first consider the case that a CEiffel program is executed by one heavyweight, threaded process. Synchronous invocation need not allocate a new thread: either the current thread just makes a procedure call, or a message is passed to an existing thread associated with the invoked object. Threads are typically associated with autonomous objects (one thread will suffice if the object is atomic). Asynchronous objects will often be handled in the same way, especially if they are atomic. It would be foolish to fork a thread for each asynchronous activity if the activities have to be serialized anyway.

We are working on a precompiler from CEiffel to Eiffel, employing a modified Eiffel-3 parser as a front end [10]. Given a class A, the precompiler derives a subclass A' which implements the type A--!--. A' also inherits from another class that provides access to the low-level primitives of a threading system. This system has also been implemented using Eiffel-3 (plus a few assembly language fragments). We hope that its portable design will facilitate implementing CEiffel on platforms different from the current one (SUNOS).

Distributed implementation of CEiffel programs is investigated in a project called HERON [8]. We are developing a platform for distributed execution of both Eiffel and CEiffel programs in heterogeneous environments. The HERON system has three components: communication support (based on standard protocols), a stub generator and a configuration tool. As opposed to the Distributed Eiffel system mentioned earlier, distribution is not reflected in the language, and we do not rely on a special operating system. Distribution transparency makes the relationship between activities and threads even fuzzier: while one object may be invoked by a procedure call, another object of the same class, if residing in a different heavyweight process, will be invoked via message passing. The programmer is, of course, not concerned with any of these details.

Acknowledgment

I am grateful to Murat Karaorman, who provided very detailed comments, and discovered an error in a draft version. Thanks also go to Sabine Finke. Olaf Langmack, Jacek Passia, Irina Piens and Thomas Wolff for several fruitful discussions on the design of CEiffel and on earlier drafts of the article. The threading system has been developed by Sabine Finke and Peter Jahn. Project HERON is supported in part by the German Science Foundation (DFG).

References

[1.] Agha, G.A., Hewitt, C., Wegner, P. and Yonezawa, A., Eds. In Proceedings of OOPSLA/ECOOP '90 Workshop on Object-Based Concurrent Programming. ACM OOPS Messenger 2, 2 (Apr. 1991).

[2.] Agha, G.A., Wegner, P. and Yonezawa, A., Eds. In Proceedings of ACM Sigplan Workshop on Object-Based Concurrent Programming. ACM SIGPLAN Not. 24, 4 (Apr. 1989).

[3.] America, P.H.M. Pool-T: A parallel object-oriented language. In [23].

[4.] America, P.H.M. and van der Linden, F. A parallel object-oriented language with inheritance and subtyping. In Proceedings of OOPSLA/ECOOP '90. ACM SIGPLAN Not. 25, 10 (Oct. 1990).

[5.] Andler, S. Predicate path expressions. In Proceedings of 6. POPL, ACM, 1979.

[6.] Caromel, D. Service, asynchrony and wait-by-necessity. JOOP 2.4 (Nov./Dec. 1989).

[7.] Caromel, D. Toward a method of object-oriented concurrent programming. Commun. ACM 36, 9 (Sept. 1993).

[8.] Finke, S., Jahn, P., Langmack, O., Lohr, K.-P., Piens, I. and Wolff, T. Distribution and inheritance in the HERON approach to heterogeneous computing. In Proceedings of Thirteenth International Conference on Distributed Computing Systems, IEEE (Pittsburgh, May 1993).

[9.] Gehani, N.H. and Roome, W.D. Concurrent C++: Concurrent programming with class(es). S - P&E 16, 12 (Dec. 1988).

[10.] Groeber, B. and Langmack, O. A compiler front-end for Eiffel-3. Rep. B-92-25, FB Mathematik, FU Berlin, Dec. 1992.

[11.] Gunaseelan, L. and LeBlanc, R.J. Distributed Eiffel: A language for programming multi-granular distributed objects. In Proceedings of the Fourth International Conference on Computer Languages, IEEE (1992).

[12.] Krakowiak, S., Meysembourg, M., Nguyen Van, H., Riveill, M., Roisin, C. and Rousset de Pina, X. Design and implementation of an object-oriented, strongly-typed language for distributed applications. JOOP 3.3 (Sept./Oct. 1990).

[13.] Lohr, K.-P., Concurrency annotations and reusability. Rep. B-91-13, Fachbereich Mathematik, Freie Universitat Berlin, Nov. 1991.

[14.] Lohr, K.-P. Concurrency annotations improve reusability. In Proceedings of TOOLS USA '92, Prentice Hall, 1992.

[15.] Matsuoka, S., Wakita, K. and Yonezawa, A. Inheritance anomaly in object-oriented concurrent programming languages. In Research Directions in Object-Based Concurrency, G. Agha, P. Wegner, A. Yonezawa, Eds. MIT Press, 1993.

[16.] McHale, C., Walsh, B., Baker, S. and Donnelly, A. Scheduling predicates. In [21].

[17.] Meseguer, J. Solving the inheritance anomaly in concurrent object-oriented programming. In Proceedings of ECOOP '93 (Kaiserslautern, July 1993).

[18.] Meyer, B. Object-Oriented Software Construction. Prentice Hall, Englewood Cliffs, N.J., 1988.

[19.] Meyer, B. Eiffel: The Language. Prentice Hall, Englewood Cliffs, N.J., 1992.

[20.] Papathomas, M. Concurrency issues in object-oriented programming languages. In Object-Oriented Development. D.C. Tsichritzis, Centre Universitaire d'Informatique, Universite de Geneve, 1989.

[21.] Tokoro, M., Nierstrasz, O. and Wegner, P., Eds. In Proceedings of Workshop on Object-Based Concurrent Computing, ECOOP '91. LNCS 612, Springer, 1992.

[22.] Yonezawa, A., Ed. ABCL: An Object-Oriented Concurrent System. MIT Press, 1990.

[23.] Yonezawa, A. and Tokoro, M., Eds. Object-Oriented Concurrent Programming. MIT Press, 1987.
COPYRIGHT 1993 Association for Computing Machinery, Inc.
No portion of this article can be reproduced without the express written permission from the copyright holder.
Copyright 1993 Gale, Cengage Learning. All rights reserved.

Article Details
Printer friendly Cite/link Email Feedback
Title Annotation:one of eight articles on concurrent object-oriented programming; special issue; usage of annotations as sequential comments with Eiffel program development software
Author:Lohr, Klaus-Peter
Publication:Communications of the ACM
Article Type:Technical
Date:Sep 1, 1993
Words:5791
Previous Article:Systematic concurrent object-oriented programming.
Next Article:Toward a method of object-oriented concurrent programming.
Topics:

Terms of use | Privacy policy | Copyright © 2020 Farlex, Inc. | Feedback | For webmasters