12.3. Default Concurrency
The instance methods of a class use the EXPOSE instruction to define a set of object variables. This collection of variables belonging to an object is called its object variable pool. The methods a class defines and the variables these methods can access is called a scope. Rexx's default concurrency exploits the idea of scope.
The object variable pool is a set of object subpools, each representing the set of variables at each scope of the inheritance chain of the class from which the object was created. Only methods at the same scope can access object variables at any particular scope. This prevents any name conflicts between classes and subclasses, because the object variables for each class are in different scopes.
If you do not change the defaults, only one method of a given scope can run on a single object at a time. Once a method is running on an object, the language processor blocks other methods on other activities from running in the same object at the same scope until the method that is running completes. Thus, if different activities send several messages within a single scope to an object the methods run sequentially.
The next example shows how the default concurrency works.
Example 12.3. Default concurrency for methods
/* Example of default concurrency for methods of different scopes */
object1 = .subexample~new
say object1~repeat(8, "Object 1 running call 1") /* These calls run */
say object1~repeater(8, "Object 1 running call 2") /* concurrently */
say "Main ended."
exit
::class example
::method repeat
use arg reps,msg
reply "Repeating" msg"," reps "times."
do reps
say msg
end
::class subexample subclass example
::method repeater
use arg reps,msg
reply "Repeating" msg"," reps "times."
do reps
say msg
end
The preceding example produces output such as the following:
Repeating Object 1 running call 1, 8 times.
Object 1 running call 1
Repeating Object 1 running call 2, 8 times.
Object 1 running call 1
Object 1 running call 2
Main ended.
Object 1 running call 1
Object 1 running call 2
Object 1 running call 1
Object 1 running call 2
Object 1 running call 1
Object 1 running call 2
Object 1 running call 1
Object 1 running call 2
Object 1 running call 1
Object 1 running call 2
Object 1 running call 1
Object 1 running call 2
Object 1 running call 2
The following example shows that methods of the same scope do not run concurrently by default.
Example 12.4. Default concurrency for methods
/* Example of methods with the same scope not running concurrently*/
object1 = .example~new
say object1~repeat(10,"Object 1 running call 1") /* These calls */
say object1~repeat(10,"Object 1 running call 2") /* cannot run */
say "Main ended." /* concurrently. */
exit
::class example
::method repeat
use arg reps,msg
reply "Repeating" msg"," reps "times."
do reps
say msg
end
The REPEAT method includes a REPLY instruction, but the methods for the two REPEAT messages in the example cannot run concurrently. This is because REPEAT is called twice at the same scope and requires exclusive access to the object variable pool. The REPLY instruction causes the first REPEAT message to transfer its exclusive access to the object variable pool to a new activity and continue execution. The second REPLY message also requires exclusive access and waits until the first method completes.
If the original activity has more than one method active (nested method calls) with exclusive variable access, the first REPLY instruction is unable to transfer its exclusive access to the new activity and must wait until the exclusive access is again available. This may allow another method on the same object to run while the first method waits for exclusive access.
12.3.1. Sending Messages within an Activity
Whenever a message is invoked on an object, the activity acquires exclusive access (a lock) for the object's scope. Other activities that send messages to the same object that required the locked scope waits until the first activity releases the lock.
Suppose object A is running method Y, which includes:
self~z
Sequential processing does not allow method Z to begin until method Y has completed. However, method Y cannot complete until method Z runs. A similar situation occurs when a subclass's overriding method does some processing and passes a message to its superclasses' overriding method. Both cases require a special provision: If an invocation running on an activity sends another message to the same object, this method is allowed to run because the activity has already acquired the lock for the scope. This allows nested, nonconcurrent method invocations on a single activity without causing a deadlock situation. The language processor regards these additional messages as subroutine calls.
Here is an example showing the special treatment of single activity messages. The REPEATER and REPEAT methods have the same scope. REPEAT runs on the same object at the same time as the REPEATER method because a message to SELF runs the REPEAT method. The language processor treats this as a subroutine call rather than as concurrently running two methods.
Example 12.5. Sending a message to SELF
/* Example of sending message to SELF */
object1 = .example~new
object2 = .example~new
say object1~repeater(10, "Object 1 running")
say object2~repeater(10, "Object 2 running")
say "Main ended."
exit
::class example
::method repeater
use arg reps,msg
reply "Entered repeater."
say self~repeat(reps,msg)
::method repeat
use arg reps,msg
do reps
say msg
end
return "Repeated" msg"," reps "times."
The activity locking rules also allow indirect object recursion. The following figure illustrates indirect object recursion.
Method M in object A sends object B a message to run method N. Method N sends a message to object A, asking it to run method O. Meanwhile, method M is still running in object A and waiting for a result from method N. A deadlock would result. Because the methods are all running on the same activity, no deadlock occurs.