Coroutines

The term coroutines refers to a group of processes that exist simultaneously but take turns executing, so that only one of the processes is executing at any given time. Coroutines offer some of the advantages of asynchronous processes, but generally are easier to design because coroutines execute in a sequential order that prevents any ambiguities of timing. The use of coroutines offers the following benefits:

  • The ability to execute a procedure repeatedly without incurring the processor time required to enter or initiate the procedure each time

  • The ability to execute a procedure repeatedly without losing the values of objects declared in the procedure between each execution

Note, however, that coroutines use the processor less efficiently than do asynchronous processes. Only one coroutine runs at a time, and there might be periods when the processor is unused because the coroutine is waiting for an I/O operation to complete. Furthermore, the statements coroutines use to transfer control to other coroutines use more processor time than the event-related functions that asynchronous processes can use to suspend or resume each other.

Creating Coroutines

An ALGOL or COBOL process can create a coroutine by executing a CALL statement. The new process and its initiator are referred to as coroutines. When the initiator executes a CALL statement, the initiator temporarily ceases execution and its stack state becomes “TO BE CONTINUED.” The stack state can be displayed by using the Y (Status Interrogate) system command. A coroutine with this stack state is referred to as a continuable coroutine.

The new process has one of the stack states that indicate the process is being processed, or soon will be, such as ALIVE or READY. The new process is referred to as an active coroutine.

The total number of coroutines increases each time an active coroutine executes a CALL statement. The new process created is an active coroutine and all others are continuable coroutines.

The concept of a coroutine is closely related to that of a synchronous process, as defined in Synchronous Processes. Every synchronous process is also a coroutine; however, not every coroutine is a synchronous process. An asynchronous process can execute a CALL statement and thus become a continuable coroutine.

Using Continue Statements

An active coroutine can transfer control to a continuable coroutine by executing an ALGOL CONTINUE statement or a COBOL CONTINUE or EXIT PROGRAM statement. For convenience, these are all referred to as continue statements in the following discussion.

The other programming languages (FORTRAN77, Pascal, RPG, and WFL) do not provide continue statements. Therefore, processes other than ALGOL or COBOL processes can be considered coroutines only in a restricted sense. For example, a WFL job can create a synchronous offspring by executing a RUN statement. The stack state of the WFL job then becomes “TO BE CONTINUED.” However, the system does not allow the offspring to use a continue statement to transfer control to the WFL job. Instead, the system automatically continues the WFL job when the offspring terminates. This act is referred to as an implicit continue and is discussed further in “Continuing the Partner Process” in this section.

To understand the effects of a continue statement, suppose an active coroutine called A executes a continue statement that specifies a continuable coroutine called B. When the continue statement is executed, the coroutine A ceases execution and coroutine B resumes execution. In other words, coroutine A becomes a continuable coroutine and coroutine B becomes an active one. Control passes from coroutine A to coroutine B.

Coroutine B can later reverse this situation by executing a continue statement that passes control back to coroutine A. However, control does not always have to pass back and forth between the same pair of processes. For example, coroutine B might continue another coroutine called C and that coroutine might then continue coroutine A.

Control can pass between coroutines any number of times. In the course of its lifetime, a coroutine can execute many continue statements applying to any number of other processes. However, for a continue statement to be successful, it must be executed by an active coroutine and it must specify a continuable coroutine. The continue statement results in an “ILLEGAL VISIT” error if it transfers control to a process that is not a continuable coroutine.

Coroutines usually belong to the same process family because continue statements must explicitly or implicitly specify the task variable of the process to be continued. A process usually has access only to the task variables of processes in its own process family. Process families are defined under Process Families in this section. The means of accessing the task variables of related processes are discussed under Accessing Task Variables in this section.

Determining Where Execution Resumes

When any coroutine continues an ALGOL coroutine, the ALGOL coroutine resumes at the point where it left off. Thus, if an ALGOL coroutine executes a CALL statement, it later resumes with the first statement after the CALL statement. If an ALGOL coroutine executes a CONTINUE statement, it later resumes with the first statement after the CONTINUE statement.

By contrast, a COBOL coroutine can resume execution at either of two points. If a COBOL coroutine executes a CONTINUE statement or an EXIT PROGRAM RETURN HERE statement, then the coroutine later resumes at the point where it left off. However, if a COBOL coroutine executes a simple EXIT PROGRAM statement, then the coroutine later resumes with the first statement in the program. (Certain limitations on the EXIT PROGRAM statement are discussed under “Continuing the Partner Process” later in this section.)

Block Structure and Coroutines

Continue statements can occur in any of the procedures executed by a process. For example, a process can execute a continue statement and, after being continued later on, can enter another procedure and execute another continue statement. Both of those continue statements can transfer control to the same coroutine, or they can transfer control to different coroutines.

If a coroutine uses a continue statement to resume its parent, and the parent exits the critical block for that coroutine, then the parent is terminated with a “CRITICAL BLOCK EXIT” error. The methods of preventing a critical block exit are discussed under “Critical Blocks” in this section.

Continuing the Partner Process

There are two types of continue statements: specific continue statements and general continue statements.

A specific continue statement is one that specifies a task variable. An ALGOL example of a specific continue statement is CONTINUE (T1). A COBOL example of a specific continue statement is CONTINUE T1. Either of these statements continues the coroutine specified by the task variable T1.

A general continue statement does not specify a task variable. In ALGOL, the general continue statement is CONTINUE. In COBOL, the general continue statement is EXIT PROGRAM or EXIT PROGRAM RETURN HERE.

The effect of the general continue statement is to continue the partner process. The partner process is the process specified by the PARTNER task attribute. This task attribute is said to be task-valued because it accesses the task variable of a particular process. For a synchronous process, the system assigns the initiating process as the partner process by default. You can design a program to assign a different task variable to the PARTNER task attribute. Thereafter, any general continue statements affect the process with that task variable.

When a synchronous process terminates, the system implicitly continues the partner process. This is the reason the initiating process usually resumes after a synchronous process terminates. However, if a synchronous process has another task variable assigned to the PARTNER task attribute, then the system continues that partner process rather than the initiating process.

Setting the PARTNER task attribute to a process other than the initiator is not recommended. Such a practice causes general continue statements or implicit continues to consume more processor time than they otherwise would. This practice also leads to source code that is difficult to understand and maintain.

A process can interrogate the PARTNEREXISTS task attribute to determine whether the current partner process is in a continuable state. This can be a useful method for avoiding “ILLEGAL VISIT” errors.

For further information regarding the PARTNER and PARTNEREXISTS task attributes, see the discussions of these attributes later in this guide.

Communication between Coroutines

When an active coroutine becomes a continuable coroutine, or vice versa, objects declared by the coroutine retain their values and are not reinitialized.

Nevertheless, the values of objects declared by a continuable coroutine can be changed by any active coroutine having access to those objects. For example, if a process executes a CALL statement, passing call-by-reference parameters, the process becomes a continuable coroutine. The offspring process is an active coroutine and can change the values of the call-by-reference parameters. The offspring process can use this method to communicate information to the parent process. When the parent process is continued, it can check to see if the parameter values were changed.

Similar considerations apply to the task attributes of a coroutine. An active coroutine can read or assign the task attributes of other coroutines, including continuable coroutines. When a continuable coroutine is continued, it can check its task attribute values to see if any were changed.

Complex Coroutine Structures

The continue statements enable you to develop complex coroutine structures that do not exactly correspond to the classical model of coroutines. A complex coroutine structure is one in which two or more active coroutines exist at the same time. In a simple coroutine structure, only one of the coroutines is active at a time.

A complex coroutine structure can result, for example, if a process called INITP initiates an asynchronous offspring called PROCP, and then initiates a synchronous offspring called CALLP. While INITP is waiting for CALLP to complete, INITP is in a “TO BE CONTINUED” state. PROCP can, therefore, execute a continue statement that causes INITP to resume. In this case, PROCP becomes a continuable coroutine and INITP and CALLP are active coroutines at the same time.

In general, the use of complex coroutine structures is not recommended because they lack the simplicity that is the primary benefit of using coroutines.