SCOOP: Simple Concurrent Object-Oriented Programming
SCOOP is an object-oriented programming model for concurrency. It aims to make concurrent programming easier by allowing programmers to apply sequential reasoning techniques when writing concurrent programs. In particular, the model guarantees the absence of common concurrency errors, such as data races.
Trying it out
You can try SCOOP without the need to install anything using our online interface:
The link above takes you to an implementation of the producer-consumer problem in SCOOP, which we discuss below. Another example is the barbershop problem.
The SCOOP model associates every object with a thread of execution, called its handler. A processor is an autonomous thread of control capable of executing actions on objects. A program variable x can point to an object with the same handler, or to an object with a different handler; in the latter case the reference is said to be separate. The semantics of a call x.f depends on this distinction: if x is not separate, the call is synchronous; if x is separate, the call will be executed asynchronously, by the handler of x. This possibility of asynchronous calls is the main source of concurrency in SCOOP.
Consider the producer-consumer problem as a simple illustration of these concepts. A root class defines the entities producer and consumer. The keyword separate specifies that these entities may be assigned to a handler different from the current one.
producer: separate PRODUCER consumer: separate CONSUMERBoth the producer and the consumer access an unbounded buffer
buffer: separate BUFFER [INTEGER]using routines consume and produce. Since producer is separate, a call such as
producer.produce (buffer)is executed asynchronously from the calling thread, by the handler of producer.
The producer-consumer problem requires that data races on buffer are avoided. SCOOP uses runtime mechanisms that ensure race freedom without the need for explicit synchronization statements in the program. Consider this implementation of routine produce:
produce (buffer: separate BUFFER [INTEGER]) -- Produce an item and put it into the buffer. local p: INTEGER do p := new_item buffer.put (p) endSince buffer is a separate argument to the routine, the runtime ensures to give routine produce exclusive access to the handler of buffer during the execution of its body. This ensures that the call buffer.put (p) cannot conflict with other concurrent access attempts to the buffer.
Besides preventing data races on buffer, the producer-consumer problem requires that consumers do not access the buffer if it is empty. Synchronizing on conditions such as "the buffer is not empty" can be expressed in SCOOP using wait conditions: preconditions of a routine (require keyword) make the execution of the routine wait until the condition is true. For example, the precondition of the consume routine ensures that the routine will wait until the buffer is not empty.
consume (buffer: separate BUFFER [INTEGER]) -- Consume an item from the buffer. require not (buffer.count = 0) local c: INTEGER do c := buffer.item end