In object-oriented programming, as in the real world, objects interact with each other. Assume, for example, throngs of people interacting at rush hour in the business district of a big city. A program that aspires to simulate the real world would have to enable many objects to interact at any given time. That could mean thousands of objects sending messages to each other, thousands of methods running at once. In Rexx, this simultaneous activity is called concurrency. To be precise, the concurrency is object-oriented concurrency because it involves objects, as opposed to, for example, processes or threads.
Rexx objects are inherently concurrent, and this concurrency takes the following forms:
Inter-object concurrency, where several objects are active—exchanging messages, synchronizing, running their methods—at the same time
Intra-object concurrency, where several methods are able to run on the same object at the same time
The default settings in Rexx allow full inter-object concurrency but limited intra-object concurrency. Some situations, however, call for full intra-object concurrency.
Rexx provides for inter-object concurrency, where several objects in a program can run at the same time, in the following ways:
By early reply, using the REPLY instruction
Using message objects
Early reply allows the object that sends a message to continue processing after the message is sent. Meanwhile, the receiving object runs the method corresponding to the message. This method contains the REPLY instruction, which returns any results to the sender, interrupting the sender just long enough to reply. The sender and receiver continue operating simultaneously.
Alternatively, an independent message object can be created and sent to a receiver. One difference in this approach is that any reply returned does not interrupt the sender. The reply waits until the sender asks for it. In addition, message objects can notify the sender about the completion of the method it sent, and even specify synchronous or asynchronous method activation.
The chains of execution represented by the sender and receiver methods are called activities. An activity is a thread of execution that can run methods concurrently with methods on other activities. In other words, activities can run at the same time.
An activity contains a stack of invocations that represent the Rexx programs running on the activity. An invocation can be:
A main program invocation
An internal function or subroutine call
An external function or subroutine call
An INTERPRET instruction
A message invocation
An invocation is pushed onto an activity when an executable unit is invoked. It is removed (or popped) when execution completes.
Every object has its own set of instance variables. These are variables associated solely with the object. When an object's method runs, it first identifies the object variables it intends to work with. Technically, it "exposes" these variables, using the Rexx instruction EXPOSE. Exposing the object's variables distinguishes them from variables used by the method itself, which are not exposed. Every method an object owns—that is, all the instance methods in the object's class—can expose variables from the object's instance variables.
Therefore, an object's instance variables includes variables:
Exposed by methods defined by the object's class. This set of variables is called a variable pool.
Exposed by methods defined by other classes in the inheritance hierarchy. The methods of each class share variables in a pool scoped to just that class.
A class's variable pool, together with the methods that expose them, are called a class scope. Rexx exploits this class scope to achieve concurrency. To explain, the object's instance variables are contained in a collection of variable pools. Each pool is at a different scope in the object's inheritance chain. Methods defined at different class scopes do not directly share data and can run simultaneiously.
Scopes, like objects, hide and protect data from outside manipulation. Methods of the same scope share the variable pool of that scope. The scope shields the variable pool from methods operating at other scopes. Thus, you can reuse variable names from class to class, without the variables being accessed and possibly corrupted by a method outside their own class. So class scopes divide an object's instance variables into pools that can operate independently of one another. Several methods can use the same object instance variables concurrently, as long as they confine themselves to variables in their own scope.
Even with class scopes and subpools, a variable is vulnerable if several methods within the scope try to access it at the same time. To handle this, Rexx ensures that when a particular method is activated and exposes variables from its scope, that method has exclusive use of the scope variable pool until processing is complete. Until then, Rexx delays the execution of any other method that needs the same scope variables.
Thus if different activities send several messages to the same object, Rexx forces the methods to run sequentially within a single scope. This "first-in, first-out" processing of methods in a scope prevents them from simultaneously accessing one variable, and possibly corrupting the data.
Rexx makes one exception to sequential processing—when a method sends a message to itself. Assume that method M1 has exclusive access to object O, and then tries to run a second, internal method M2, also belonging to O. Internal method M2 would try to run, but Rexx would delay it until the original method M1 finished. Yet M1 would be unable to proceed until M2 ran. The two methods would become deadlocked. In actual practice Rexx intervenes by treating internal method M2 like a subroutine call. In this case, Rexx runs method M2 immediately, then continues processing method M1.
The mechanism controlling this is the activity. Typically, whenever a message is invoked on an object, the activity acquires exclusive access by locking the object's scope. Any other activity sending a message to the object whose scope is locked must wait until the first activity releases the lock. The situation is different, however, if the messages originate from the same activity. When an invocation running on an activity sends another message to the same object, the method is allowed to run because the activity has already acquired the lock for the scope. Thus, Rexx permits nested, nonconcurrent method invocations on a single activity. No deadlocks occur because Rexx treats these additional messages as subroutine calls.
Several methods can access the same object at the same time only if they are operating at different scopes. That is because they are working with separate variable subpools. If two methods in the same scope try to run on the object, Rexx by default processes them on a "first-in, first-out" basis, while treating internal methods as subroutines. You can, however, achieve full intra-object concurrency. Rexx offers several mechanisms for this, including:
The UNGUARDED option of the ::METHOD directive, which provide unconditional intra-object concurrency.
The GUARD OFF and GUARD ON instructions, which permit switching between intra-object and default concurrency.
When intra-object concurrency at the scope level is needed, you must specifically employ these mechanisms (see the following section). Otherwise, Rexx sequentially processes the methods when they are competing for the same object variables.
By default, Rexx assumes that an active method requires exclusive use of its scope variable pool. If another method attempts access at that time, it is locked out until the first method finishes. This default intra-object concurrency maintains the integrity of the variable pool and prevents unexpected results. Rexx manages queues for incoming requests that result in messages being sent to the same object.
Some methods can run concurrently without affecting variable pool integrity or yielding unexpected results. When a method does not need exclusive use of its object variable pool, the UNGUARDED option of the ::METHOD directive to provide unconditional intra-object concurrency. These mechanisms control the locking of an object's scope when a method is invoked.
Many methods cannot UNGUARDED because they sometimes require exclusive use of their variable pool. At other times, they must perform some action that involves the concurrent use of the same pool by a method on another activity. In this case, you can use the GUARD built-in function. When the method reaches the point in its processing where it no longer requires exclusive use of the variable pool it can use the GUARD OFF instruction to allow methods running on different activities to become active on the same scope. If the method needs to regain exclusive use, it calls GUARD ON.
For more flexibility when activating methods, you can use GUARD ON/OFF with the "WHEN expression" option. Add this instruction to the method code at the point where exclusive use of the variable pool becomes conditional. When processing reaches this point, Rexx evaluates expression to determine if it is true or false.
For example, if you specify "GUARD OFF WHEN expression," the active method keeps running until expression becomes true. To become true, another method must assign or drop an object variable that is named in expression. Whenever an object variable changes, Rexx reevaluates expression. If expression becomes true, GUARD is turned off, exclusive use of the variable pool is released, and other methods needing exclusive use can begin running. If expression becomes false again, GUARD is turned on and the active method regains exclusive use.
Note: If expression cannot be met, GUARD ON WHEN puts the program in a continuous wait condition. This can occur in particular when several activities run concurrently. A second activity can make expression invalid before GUARD ON WHEN can use it.