Object-based concurrency
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 distributed approaches conceivable.
See slide [6-o-conc].
Object-based concurrency
- add processes -- synchronization
- multiple active objects -- rendezvous
- asynchronous communication -- message buffers
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).
The disadvantage of this somewhat naive 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, 1991)
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 to be preferred, 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 (see America, 1987).
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 comes 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].