[comp.object] Concurrency in Object-Oriented Systems

eberard@ajpo.sei.cmu.edu (Edward Berard) (10/15/89)

Earlier, Joe Pallas (pallas@Neon.Stanford.EDU) wrote: 

> Don't make object-oriented programming harder than it needs to be: if
> your system has no concurrency, then you don't have to worry about
> concurrency just because you're using object-oriented programming
> techniques or languages.

> On the other hand, if your system does have concurrency,
> object-oriented techniques MAY help to deal with it.  Or maybe
> not---this is still open, I'd say.

Before I begin, I must say that I agree with Joe when he says, in
effect, if your system has no concurrency, don't introduce concurrency
just for the sake of concurrency. However, one of the more attractive
things about an object-oriented approach is that it encourages one to
adhere to two of the most basic principles of engineering:

	1. Make the solution look like the problem.

	2. Form follows function.

Said yet another way, object-oriented solutions are supposed to model
the "real world." The closeness of our object-oriented solutions to
the real world, of course, depends on a number of items, including:

	- the current level of abstraction, i.e., as we move to lower
	  levels of abstraction, we tend to get further from the real
	  world, and closer to the machine (computer),

	- the capabilities provided to us by our choice of programming
	  language, e.g., some programming languages make it difficult
	  to easily provide a "one-to-one mapping" to real world
	  objects, 

	- the requirements (restrictions) placed on the implementation
	  by our clients, e.g., we may have to interface with an
	  existing, non-object-oriented system, and

	- probably most importantly, the skill and intuition of the
	  system designers and implementers.

The real world is much more concurrent than you might expect. We are
virtually surrounded by "objects with life" (i.e., objects which can,
and do, change their state spontaneously). Examples of objects with
life include people, temperature sensors, and electric alarm clocks.
One could have modeled the recent OOPSLA meeting as almost 2,000
concurrent objects interacting randomly (;-)).

Sometimes, those implementing object-oriented solutions either ignore
the concurrency present in real life, or handle it in an unnatural
manner. For example:

	- Many of the solutions presented at OOPSLA for the Greed game
	  (see an earlier posting for the specification) ignored the
	  fact that two, or more, dice can be rolled concurrently.
	  These solutions artificially constrained the dice to be
	  rolled individually.

	- Often "anthropomorphism" (interpretation of what is not
	  human or personal in terms of human or personal
	  characteristics) is inappropriately introduced into
	  object-oriented solutions. This frequently creates
	  additional (conceptual) threads of control where none
	  existed in the real world.

If the real world is concurrent, how do we properly introduce
concurrency into our object-oriented solutions? Let's start with
something simple and intuitive. Something that a 9 or 10 year-old
child (literally) can understand. 

Consider the discussion of "agents" at the recent OOPSLA. Agents are
composed of "agent rules." An agent rule represents a fairly atomic
unit of behavior, e.g., eat, wander, and flee. Agent rules can be
gathered into collections, i.e., agents, which represent larger
(non-atomic) types of behavior, e.g., aggressiveness. Quoting Fenton
and Beck ["Playground: An Object-Oriented Simulation System with
Agent Rules for Children of All Ages" in the proceedings of OOPSLA
'89.], "An agent rule is an independently executing entity sensitive
to particular sign stimuli."

Agents can be assigned to individual objects, with the result being
objects which display non-deterministic, and "almost life-like"
behavior. Please note that since agents represent multiple
(independent and/or interacting) threads of control, you could view
this as an example of concurrency which is _internal_ to an object. 

The Playground system (discussed in the Fenton and Beck article) is
intended for use by children. According to the article, most of the
children (who were 9 and 10 years of age) had few problems recognizing
that real world entities could be doing two, or more things at once.
For example, a fish could be eating and watching for predators at the
same time. (At last, our objects can "walk down the street and chew
gum at the same time." 8-))

Let's now shift the focus to a more technical level.

In studying software reusability, people frequently make the mistake
of assuming that the differences among reusable components are
strictly functional. In the object-oriented community, reuse often
takes the form of "robbing code" from a superclass, or a prototypical
object. Grady Booch, on the other hand, saw things in a slightly
different manner.

Booch recognized that you could take the same basic abstraction (e.g.,
a list, a set, a stack, a matrix) and create a number of different
"forms" for the abstraction. Each form represented a truly different
class. Some of the forms were very simple, for example, "bounded" and
"unbounded." Objects which were instances of classes representing
bounded forms has a fixed upper limit to the number of items they
could contain. Objects which were instances of classes representing
unbounded forms were limited only by the computational resources
(e.g., memory) as to the number of items that they could contain.

Booch, of course, described forms which provided mechanisms for
handling concurrent situations. In researching, and extending, these
forms at my former company (EVB Software Engineering, Inc.), we came
to realize that:

	- Object-oriented approaches offered a mechanism for handling
	  concurrency which is fundamentally different from the more
	  traditional approaches. In the more traditional approaches, a
	  master routine (or series of master routines) was often
	  charged with maintaining order (e.g., avoiding deadlock,
	  starvation, and race conditions) in a system. In
	  object-oriented systems, objects could be provided with a
	  means of "handling themselves" in concurrent applications.
	  This meant a shifting of responsibility, i.e., from the
	  master routine to the objects themselves. If done well, this
	  resulted in a simplification of the overall systems.

	- There are two, very different, basic forms of
	  object-oriented concurrency: operational (or class) based
	  concurrency, and object-based concurrency. In class-based
	  concurrency, the mechanisms (methods) for dealing with
	  concurrent situations were localized in the class itself.
	  This meant that all concurrent accesses to the objects
	  created using the class had to, in effect, go through the
	  class. This has the effect of "locking out" any operations
	  on other objects which are instances of the same class
	  whenever an operation is attempted on an given instance of a
	  class. 

	  Object-based concurrency, on the other hand, dictated that
	  the mechanisms for handling concurrency were localized in
	  the instances (objects) of the class, i.e., each object
	  carries with it the capability to handle concurrent
	  interactions. This means that interacting with one instance
	  of a class has no impact on interactions with other
	  instances of the same class. Thus, object-based concurrency
	  provides more "true concurrency" than does class-based
	  concurrency.

	  The main tradeoff is this: Object-based concurrency requires
	  more resources (e.g., CPU cycles and memory) than does
	  class-based concurrency.

Booch defined a number of concurrent forms for classes. We observed
that these forms could be used with either class-based concurrency or
object-based concurrency. We also added a form. These forms are:

	- "Concurrent" (we renamed this form to "protected"):
	  Objects which were instances of "concurrent" classes handle
	  concurrent interactions in a simple manner, i.e., they
	  sequentialize all accesses to the object. This is not
	  terribly concurrent, but objects "can take care of
	  themselves" in a concurrent environment.

	- "Guarded": Suppose you want to create a "macro" using two,
	  or more operations in the interface for an object. Further,
	  you want to insure that no other operations take place with
	  the object until your macro is complete. Guarded forms
	  provide their users with a semaphore which enables a user
	  to "lock" and object, perform the desired series of
	  operations, and then "unlock" the object. This has,
	  unfortunately, all the undesirable problems associated with
	  semaphores, e.g., forgetting to unlock an object when you
	  are through, "getting killed off" before you have a chance
	  to unlock the object, and forgetting to ensure that all
	  other entities abide by the semaphore rules.

	- "Multiple": Not all interactions with an object require a
	  state change for that object. It may be entirely appropriate
	  to allow multiple "readers" (i.e., interactions which cannot
	  change the state of the object), while sequentializing
	  "writers" (i.e., interactions which change the state of the
	  object). [This is the classic "multiple readers and writers"
	  concurrency problem.] Objects which are instances of classes
	  of the multiple form allow multiple readers while
	  sequentializing writers. (While conceptually very simple,
	  there are many interesting points which must be
	  appropriately addressed in the Multiple form. For example,
	  readers must have access to information which is
	  up-to-date.)

	- "Multi-Guarded" (we added this form): Multi-guarded is
	  essentially a combination of multiple and guarded.

There are many other important issues which can (and must) be
discussed in the design of concurrent object-oriented systems.
However, what is important to realize is that useful, concurrent
object-oriented systems have been (and are being) constructed using
these, and other principles.

(This message is already too long. See you later, and thanks for
listening.)

				-- Ed Berard
				   (301) 353-9652