Simple Concurrent Object-Oriented Programming Nati Fuks
SCOOP Outline SCOOP Introduction Generator SCOOP vs Java PRODUCER-CONSUMER example SCOOP Semantics Mapping from SCOOP to Eiffel+THREADs
Concurrency Concurrent programming is difficult e.g. Java Memory Flaw circa 1999 A variety of mechanisms must be mastered Thread class, synchronize, wait, notify, mutexes, monitor, critical regions Problems such as deadlock, safety and liveness properties, inheritance anomaly
SCOOP A single new keyword (separate) provides for a full-fledged concurrency mechanism !!!
SCOOP SCOOP: Simple Concurrent Object-Oriented Programming. Defined by Bertrand Meyer in OOSC in 1997 Implementations: Compton. Changes in the open source SmallEiffel compiler 2. This work 1 is now incompatible with later versions of the SmallEiffel compiler (now called SmartEiffel) also did not implement the full set of SCOOP constructs (such as contracts and “once” routines) 3. (not as complete as our approach) allows for distributed computing does not support exclusive locking of multiple concurrent objects 3. SCOOP in the .NET framework
SCOOP Generator Why SCOOP Generator? What do we achieve? Object-Oriented Design Design By Contract Simple and intuitive concurrency model of SCOOP What do we achieve? Concurrent programs using SCOOP
SCOOP Generator Eiffel SCOOP -> Eiffel + THREAD Standard Eiffel code (mutexes and THREADs) Eiffel Studio 5.4 Advantages Pure Eiffel Independence Advantages of Generator: the resulting code is pure Eiffel that compiles on standard Eiffel compilers the Generator is not dependent on changes to the standard Eiffel compilers. Only significant changes to Eiffel syntax would require (probably minor) changes to the Generator the target code will run on any platform supported by the compiler Disadvantage:debugging must currently be performed in the standard runtime systems of the target code rather than being able to work at the abstract level of SCOOP code Target code is cross-platform Disadvantage – Debugging on a target code
Producer-Consumer Java Solution
Producer-Consumer SCOOP Solution - same behaviour as the Java solution - only one extra keyword separate is used (||) - uses contracts with all the benefits of DbC
ROOT_CLASS class ROOT_CLASS creation make feature b: separate BUFFER p: PRODUCER -- declared separate c: CONSUMER -- declared separate make is -- Creation procedure. do create b.make create p.make(b) create c.make(b) end 3 attributes: buffer b, producer p and consumer c b: separate BUFFER - executes in its own logical thread (called a subsystem), all its routines are synchronized classes PRODUCER and CONSUMER are declared separate right at the class declaration (they always execute in their own subsystem) create p.make(b), create c.make(b) – both PRODUCER and CONSUMER have reference to the same BUFFER b
Bounded BUFFER class BUFFER creation make feature put (x:INTEGER) is require count <= 3 do q.put(x) ensure count = old count + 1 and q.has (x) end remove is require count > 0 do q.remove ensure count = old count - 1 …
PRODUCER separate class PRODUCER creation make feature buffer: separate BUFFER make (b: separate BUFFER) is do …keep_producing … end keep_producing is do … produce(buffer,i) … end produce (b : separate BUFFER; i:INTEGER) is require b.count <= 2 do b.put(i) ensure b.has(i) end
CONSUMER separate class CONSUMER creation make feature buffer: separate BUFFER make (b: separate BUFFER) is do …keep_consuming… end keep_consuming is do …consume(buffer)… end consume (b: separate BUFFER) is require b.count > 0 do b.remove ensure b.count = old b.count - 1 end
Synchronization in SCOOP A call to the routine produce with a separate will block until: (a) the producer gets sole access to the buffer + (b) the buffer must not be full as indicated in the precondition Thus both mutual exclusion and the validity of the contract (precondition in this example) are ensured
Preconditions in SCOOP if not buffer.full then buffer.put(value) produce (b: separate BUFFER; i: INTEGER) is require b.count <= 2 i >= 0 do b.put (i) end In sequential processing, the precondition is a correctness condition. If the precondition is true execution immediately proceeds to the body of the routine. If the precondition is false, an exception is generated The conclusion seems inescapable: we still need preconditions, if only for the supplier's sake, but they must be given a different semantics, as shown below (i >= 0) – regular precondition (b.count <= 2) - separate precondition In the concurrent case, the precondition becomes a wait condition and the class waits until the precondition evaluates to true It is only a separate precondition that delays. A non-separate precondition will act as a regular correctness condition Separate precondition must wait until every blocking object is free and every separate precondition is satisfied We have therefore changed the semantics of a precondition in sequential Eiffel as a correctness condition to a wait condition in SCOOP.
Mapping to Eiffel + THREAD separate class ROOT_CLASS class ROOT_CLASS inherit THREAD_CONTROL feature feature request_pended: INTEGER_REF requests_pended_mutex:MUTEX b:separate BUFFER p:PRODUCER b:BUFFER b_mutex: MUTEX p:PRODUCER
Mapping to Eiffel 2 make is do make is do requests_pended := 1 create b.make b_mutex.lock create b.make b_mutex.unlock
Mapping to Eiffel 3 create p.make (b) create p.make (b, b_mutex, requests_pended, requests_pended_mutex) p.launch set_feature_to_do ([Current, "KEEP_PRODUCING_ROUTINE"])
Mapping to Eiffel 4 create c.make (b) same as p.make… requests_pended_mutex.lock requests_pended.copy (requests_pended-1) requests_pended_mutex.unlock end join_all end
Contributions Analysis of Meyer’s SCOOP with a view to implementation – semantics of SCOOP via Compton’s subsystems Based on the semantics of SCOOP – provided a mapping from SCOOP to Eiffel + Threads. Generator parses SCOOP programs, flags syntax errors and automatically translates to portable executable code based on the mapping. First full implementation of SCOOP contracts routine calls with multiple separate objects
Thank You