Active objects -- synchronous Java/C++

Instructor's Guide


intro, paradigms, comparison, design, prototypes, architectures, summary, Q/A, literature
When it comes to combining objects (the building blocks in an object-oriented approach) with processes (the building blocks in parallel computing), there are three approaches conceivable. See slide 6-o-conc.

Object-based concurrency


slide: Objects and concurrency

One can simply add processes as an additional data type. Alternatively, one can introduce active objects, having activity of their own, or, one can employ asynchronous communication, allowing the client and server object to proceed independently.

Processes

The first, most straightforward approach, is to simply add processes as a primitive data type, allowing the creation of independent threads of processing. An example is Distributed Smalltalk (see Bennett, 1987). Another example is Java, which provides support for threads, synchronized methods and statements like wait and notify to protect re-entrant concurrent methods. The disadvantage of this approach, however, is that the programmer has full responsibility for the most difficult part of parallel programming, namely the synchronization between processes and the avoidance of common errors such as simultaneously assigning a value to a shared variable. Despite the fact that the literature, see  [Andrews], abounds with primitives supporting synchronization (such as semaphores, conditional sections and monitors), such an approach is error-prone and means a heavy burden on the shoulders of the application developer.

Active objects

A second, and in my view preferable, approach is to introduce explicitly a notion of active objects. Within this approach, parallelism is introduced by having multiple, simultaneously active objects. An example of a language supporting active objects is POOL, described in  [Am87]. Communication between active objects occurs by means of a (synchronous) rendezvous. To engage in a rendezvous, however, an active object must interrupt its own activity by means of an (Ada-like) accept statement (or answer statement as it is called in POOL), indicating that the object is willing to answer a message. The advantage of this approach is, clearly, that the encapsulation boundary of the object (its message interface) can conveniently be employed as a monitor-like mechanism to enforce mutual exclusion between method invocations. Despite the elegance of this solution, however, unifying objects and processes in active objects is not without problems. First, one has to decide whether to make all objects active or allow both passive and active objects. Logically, passive objects may be regarded as active objects that are eternally willing to answer every message listed in the interface description of the object. However, this generalization is not without penalty in terms of runtime efficiency. Secondly, a much more serious problem is that the message-answering semantics of active objects is distinctly different from the message-answering semantics of passive objects with respect to self-invocation. Namely, to answer a message, an active object must interrupt its own activity. Yet, if an active object (in the middle of answering a message) sends a message to itself, we have a situation of deadlock. Direct self-invocation, of course, can be easily detected, but indirect self-invocations require an analysis of the complete method invocation graph, which is generally not feasible.

Asynchronous communication

Deadlock may come about by synchronous (indirect) self-invocation. An immediate solution to this problem is provided by languages supporting asynchronous communication, which provide message buffers allowing the caller to proceed without waiting for an answer. Asynchronous message passing, however, radically deviates from the (synchronous) message passing supported by the traditional (passive) object model. This has the following consequences. First, for the programmer, it becomes impossible to know when a message will be dealt with and, consequently, when to expect an answer. Secondly, for the language implementor, allocating resources for storing incoming messages and deciding when to deal with messages waiting in a message buffer becomes a responsibility for which it is hard to find a general, yet efficient, solution. Active objects with asynchronous message passing constitute the so-called actor model, which has influenced several language designs. See  [Agha].

Synchronous C++/Java

In  [Petitpierre98], an extension of C++ is proposed that supports active objects, method calls by rendez vous and dynamic checks of synchronization conditions. The concurrency model supported by this language, which is called sC++, closely resembles the models supported by CCS, CSP and Ada.

An example of the declaration of an active object in sC++ is given in slide ex-active.


sC++



  active class S { 
  public: 
     m () { ... } 
  private: 
     @S () {  
pseudo-constructor
select { 01 -> m();
external call
instructions ... || accept m;
accept internal method
instructions ... || waituntil (date);
time-out
instructions ... || default
default
instructions ... } } }

slide: Synchronization conditions in sC++

The synchronization conditions for instances of the class are specified in a select statement contained in a constructor-like method, which defines the active body of the object. Synchronization may take place in either (external) calls to another active object, internal methods that are specified as acceptable, or time-out conditions. When none of the synchronization conditions are met, a default action may take place. In addition to the synchronization conditions mentioned, a when guard-statement may occur in any of the clauses of select, to specify conditions on the state of the object or real-time constraints.

The sC++ language is implemented as an extension to the GNU C++ compiler. The sC++ runtime environment offers the possibility to validate a program by executing random walks, which is a powerful way to check the various synchronization conditions. The model of active objects supported by sC++ has also been realized as a Java library, see  [Petitpierre99]. There is currently, however, no preprocessor or compiler for Java supporting synchronous active objects.

As argumented in  [Petitpierre98], one of the advantages of synchronous active objects is that they allow us to do away with event-loops and callbacks. Another, perhaps more important, advantage is that the model bears a close relationship with formal models of concurrency as embodied by CCS and CSP, which opens opportunities for the verification and validation of concurrent object-oriented programs. In conclusion, in my opinion, the active object model discussed deserves to become a standard for both C++ and Java, not because it unifies the concurrency model for these languages, which is for example also done by JThreads++ described in  [JThreads], but because it offers a high level of abstraction suitable for concurrent object-oriented software engineering.