Tutorial

Rationale

Concurrency, which used to be the privilege of highly specialized application domains - operating systems and network programming - now rears its head in many traditional areas. Multi-threading, in particular, is of interest to ever more applications. Yet the techniques used to program concurrent applications remain low-level, as illustrated for example by widely used multithreading libraries. The resulting applications are difficult to produce, to debug and to maintain; the code is complex, relying on low-level notions such as semaphores. There is a stunning gap between the object-oriented concepts that are increasingly popular for the architecture of sequential programs and the techniques used for handling the multithreaded or concurrent parts.

The SCOOP mechanism seeks to remove this gap by bringing to the world of concurrency the same systematic O-O development techniques that have made their mark in the sequential world.

Basic concepts

SCOOP starts from the observation that the basic framework of object-oriented computation lends itself naturally to a concurrent extension. Objects are independent entities with their own state and controlled access to their mechanisms. Contrary to most other approaches to concurrent O-O programming, we do not attempt to make objects "active", an approach that quickly lead to contradictions such as the "inheritance anomaly". Instead, SCOOP makes explicit the notion of processor, implicit in usual views of computation. Programs apply actions to objects using processors:

In sequential programming there is only one processor, and concurrency is defined by the presence of several allowing interleaved execution of instructions. These processors are threads of control; they can be computers ("processors") in the hardware sense, but also processes or threads on an operating system. To write a SCOOP program, one need not know how processors are actually implemented; the correspondence between the abstract architecture and the actual processors, software -or hardware- based, is described by a Concurrency Control File (CCF) independent from the rest of the application.

The following figure illustrates this architectural decision: the SCOOP level provides the general mechanisms that all SCOOP programmers will use. The lower level provides "handles", each based on a certain concurrency mechanism implementing its own view of processors and the associated facilities.

All calls to operations on a particular object are handled by a single processor; we say that the processor handles the object. With these basic concepts, we may isolate the difference between sequential and concurrent computation down to a single key point: what happens in the basic operation of O-O computation, a "feature call" of the form

  1. x.f (a)
In a sequential context this is synchronous: computation doesn't proceed until the call to f has been completed. In a concurrent context, if x denotes an object handled by a different processor (different from the processor handling the object on whose behalf the call is being executed), the communication can be asynchronous: computation can just proceed without waiting for f to terminate. That's indeed the whole idea of concurrency: several computations can proceed in parallel, not waiting for each other until they need to. When they indeed "need to" is, in SCOOP, determined not by the programmer but automatically by the SCOOP mechanisms: the processor of the client object will need to resynchronize with the processor in charge of x when its computation requires access to a query on x. This SCOOP policy is called wait by necessity.

To distinguish between synchronous and asynchronous calls, the program must specify whether the processor handling x is the same or another. This leads to the single language extension required by SCOOP: separate declarations. If x represents an object handled by another processor, it will be declared (in Eiffel syntax) not as

  1. x: SOME_TYPE
but as
  1. x: separate SOME_TYPE
This doesn't specify the processor but does specify that it is (or may be) a different one, yielding a different semantics of calls.

For simple reasons of being able to reason about programs, calls on a separate object are exclusive: only one client can use a separate supplier at a time. The mechanism for reserving an object is simply argument passing: a call of the form

  1.      g (x)
or
  1.      b.g (x)
where x is separate, will only proceed when the object attached to x becomes available to the caller; it will then retain that object for the duration of the call. Calls of the above basic form x.f (a), where x is separate, are only permitted when x is such a formal argument of the enclosing routine, here g. This rule guarantees predictability of the code and avoids major mistakes; even for an experienced concurrent programmer, it is very easy - in a context where the rule would not apply - to believe instinctively that in
  1. x.insert (a, some_position)
  2. ...
  3. y = x.value (some_position)

the element retrieved by the last instruction is the one inserted by the first instruction. But some other separate client may have polluted the structure by squeezing in another insert instruction in-between, even though this is not reflected in the code. Such bugs are very difficult to identify because they are by their very nature transient - the problem will occur only rarely, and in appearance haphazardly. The SCOOP rules guarantee that the above calls may only occur in a routine of which x is a separate argument. So the intuitive expectation that the two calls act on the same object with no competing access in-between - as suggested by the code - indeed matches reality. If this property is not required, the calls to insert and value may just appear in different routines of the class, for a finer level of access control granularity.

The final synchronization mechanism is provided by a natural extension of the Design by Contract constructs of Eiffel. A precondition on a separate target, as in

  1. insert (structure: CONTAINER;  element: SOME_TYPE; position: INTEGER)
  2.     require
  3.         structure_not_void: structure =/ void
  4.         structure_not_full: not structure.is_full
  5.         element_not_void: element /= void
  6.         valid_position: structure.is_valid_index (position)
  7.     do
  8.         ...
  9.     ensure
  10.         ...
  11.     end
cannot keep its usual semantics of a correctness condition, because even if the client ensures it prior to a call some other client can invalidate it before the routine actually starts its execution, so that the routine would be wrong in assuming the precondition. It has to be reinterpreted as a wait condition. Hence a call such as
  1. insert (s, e, p)
will proceed only when s is (as noted before) available on an exclusive basis to the client, and satisfy the precondition. This convention provides a simple and powerful synchronization technique.

These are the basic concepts of SCOOP. They are complemented by a few library mechanisms that tune the mechanism, for example to specify limits on the acceptable waiting time when trying to reserve an object.

The result is a general mechanism that makes it possible to program concurrent applications in a much simpler way than we have seen anywhere else. Check the numerous examples in the references below to see for yourself.

Bibliography

  • Meyer B.: Object-Oriented Software Construction, chapter 31, 2nd edition, Prentice Hall, 1997.
  • Nienaltowski P., Meyer, B.: Contracts for concurrency, International Symposium on Concurrency, Real-Time and Distribution in Eiffel-like Languages (CORDIE), 4-5 July 2006, York, UK. paper
  • Nienaltowski P.: Flexible locking in SCOOP, International Symposium on Concurrency, Real-Time and Distribution in Eiffel-like Languages (CORDIE), 4-5 July 2006, York, UK. paper
  • Arslan V., Eugster P., Nienaltowski P., Vaucouleur S.: SCOOP - concurrency made easy, in Meyer B., Schiper A., Kohlas J. (Eds) Dependable Systems: Software, Computing, Networks, Springer-Verlag, 2006 (to appear).
  • Arslan V., Eugster P., Nienaltowski P.: Modeling embedded real-time applications with objects and events, 12th IEEE Real-Time and Embedded Technology and Applications Symposium RTAS 2006, April 2006, San Jose, USA. paper
  • Nienaltowski P., Arslan V., Meyer B.: Concurrent object-oriented programming on .NET, IEE Proceedings Software, Special Issue on ROTOR, vol. 150, no. 5, 308-314, October 2003 paper
  • Nienaltowski P.: Efficient data race and deadlock prevention in concurrent object-oriented programs, Doctoral Symposium, OOPSLA'04, Vancouver, Canada, October 2004. Appears in OOPSLA 2004 Companion: 56-57 paper
  • Nienaltowski P., Arslan V., Meyer B.: SCOOP: Concurrent Programming Made Easy draft