Copy Link
Add to Bookmark
Report

Chapter 15 - Concurrency

eZine's profile picture
Published in 
Modula2
 · 30 Jan 2023

PREREQUISITES FOR THIS MATERIAL

In order to understand this material you should have a good grasp of the material in Part I of this tutorial and at least a cursory knowledge of the material in chapter 14 concerning the pseudo module SYSTEM and especially the ADR and SIZE functions.

WHAT IS CONCURRENCY

True concurrency is when two processes are taking place at exactly the same time with neither affecting the other in a degrading way. This is possible in many areas of your life, such as when the Grandfather clock is running at the same time as the furnace and the television set. These are different processes all running at the same time. In the field of computers, true concurrency requires at least two separate processors that are running completely separate programs or different parts of the same program.

Usually, when computers are said to be running concurrent processes, reference is being made to a time sharing situation in which two or more programs are swapped in and out of the computer at a rapid rate, each getting a small slice of time, a millisecond being typical. Each process appears to be running constantly but is in fact only running a small part of the time, sort of stuttering. The swapping in and out is taking place so fast that, to the casual user, it appears that the entire computer is working on only his program.

WHAT IS MODULA-2 CONCURRENCY?

The process in Modula-2, which is called concurrency, is neither of the above, and it should probably not be called concurrency. It is a new way to call and return from procedures, and although it is possible to use this new method of procedure linkage to simulate something that looks like concurrent processes, it is not true concurrency. It is a part of the language, and a useful part, so we will cover it in this chapter.

OUR FIRST COROUTINE

Load and display the program named COROUT.MOD for our first look at this new technique.

COROUT.MOD 

(* Chapter 15 - Program 1 *)
MODULE Corout;

FROM InOut IMPORT WriteCard, WriteString, WriteLn;

FROM SYSTEM IMPORT WORD, PROCESS, ADR, SIZE,
NEWPROCESS, TRANSFER;

VAR main, Process1, Process2 : PROCESS;
WorkSpace1, WorkSpace2 : ARRAY[1..300] OF WORD;
Index : CARDINAL;

PROCEDURE MainProcess;
BEGIN
FOR Index := 1 TO 5 DO
WriteString('This is loop');
WriteCard(Index,2);
IF Index > 2 THEN
TRANSFER(Process1,Process2);
WriteString(' and back to main loop');
END;
WriteLn;
END; (* of FOR loop *)
WriteString('End of the MainProcess loop');
WriteLn;
TRANSFER(Process1,main);
END MainProcess;

PROCEDURE SubProcess;
BEGIN
LOOP
WriteString(' in SubProcess');
TRANSFER(Process2,Process1);
WriteString(' back');
END;
END SubProcess;

BEGIN (* Main Module Body *)
NEWPROCESS(MainProcess,ADR(WorkSpace1),SIZE(WorkSpace1),
Process1);
NEWPROCESS(SubProcess,ADR(WorkSpace2),SIZE(WorkSpace2),
Process2);
TRANSFER(main,Process1);
WriteString('End of the program');
WriteLn;
END Corout.

Without lots of explanation, there are lots of new items IMPORTED and a few variables defined, the most interesting being the three that are defined as PROCESS type. These will be the three processes we will use in this example. Next, there are two PROCEDURES which define what the coroutines will do. It must be noted that these PROCEDURES are not allowed to have any formal parameters in their headers. Finally we come to the main program which is where we will start to define how to use the coroutines.

First, in the main program, we call "NEWPROCESS" which loads a new process without running it. We give it the name of the procedure where the process can be found, and give the system a workspace by assigning a previously defined array to it. This is done by giving the system the address and the size of the array. Finally, we give it a name by which we can call the process. It must be noted that the workspace must be sufficient for the new process to store all of its variables, so give the process a generous workspace. If it is too small, a runtime error will be generated. We then load a second process in preparation for running the program by calling NEWPROCESS again.

HOW DO WE GET IT STARTED?

When we began the main program, we actually loaded and started a process, the one we are presently executing. We have done this for every program in this tutorial so far but paid no attention to it as far as referring to it as a process. We have not given our running process a name yet, but we will when we leave it because we have defined another PROCESS type variable named "main". To start the next process we use the TRANSFER function with the name of the process we wish to terminate and the one we wish to start. This is illustrated in line 43 from which we jump to line 15 in the process named "Process1". In Process1 we begin a FOR loop where we write a string and a cardinal number then when "Index" exceeds 2, we do another TRANSFER, this time from Process1 to Process2. Thus we jump from line 19 in one procedure to line 31 in another where we begin an infinite loop. We print another string in line 32 and once again do a TRANSFER from line 33 to somewhere. The place where we go at this point is what makes the coroutines different from the standard procedure.

WHERE DO WE GO WHEN WE RETURN TO A COROUTINE?

Instead of jumping to the beginning of the procedure again, which would be line 15 in this example, we return to the statement following the one we left from. In this case we will return from line 33 to line 20 and complete the loop we started earlier. When Index is increased to 4, we will once again TRANSFER control from line 19 to "Process2", but instead of jumping to line 31 we will return where we left off there at line 34. That loop will complete, and we will once again return to line 20. When the FOR loop in "Process1" finishes, we execute lines 24 and 25 then the TRANSFER in line 26 will return us to the main module body where we will arrive at line 44 and complete the last two write statements and do a normal exit to the operating system.

Rather than a technical discussion of how to use coroutines, this example was defined step by step. If it was not clear, reread the entire description until you understand it. There is really nothing else there is to know about coroutines other than that knowledge gained by experience in using them, which is true of any new principle in computer programming or any other facet of life.

WHAT WAS ALL THAT ABOUT?

The thing that is really different about coroutines is the fact that they are both (or all three or more) loaded into the computers memory and they stay loaded for the life of the program. This is not true of normal procedures. It is transparent to you, but procedures are not all simply loaded into the computer and left there, they are dynamically allocated and started as they are called. That is why variables are not saved from one invocation of a procedure to the next. The variables within a process are loaded once, and stay resident for the life of the program.

In the example program on your monitor, all three processss including the main program are loaded and none is really the main program since any of the programs have the ability to call the others.

Load and display COROUT2.MOD for the second example with coroutines.

COROUT2.MOD 

(* Chapter 15 - Program 2 *)
MODULE Corout2;

FROM InOut IMPORT WriteCard, WriteString, WriteLn;

FROM SYSTEM IMPORT WORD, PROCESS, ADR, SIZE,
NEWPROCESS, TRANSFER;

VAR main, Process1, Process2 : PROCESS;
WorkSpace1, WorkSpace2 : ARRAY[1..300] OF WORD;
Index : CARDINAL;

PROCEDURE WriteStuff;
BEGIN
WriteString('This is loop');
WriteCard(Index,2);
END WriteStuff;

PROCEDURE MainProcess;
BEGIN
FOR Index := 1 TO 5 DO
WriteStuff;
IF Index > 2 THEN
TRANSFER(Process1,Process2);
WriteString(' and back to main loop');
END;
WriteLn;
END;
WriteString('End of the MainProcess loop');
WriteLn;
TRANSFER(Process1,main);
END MainProcess;

PROCEDURE SubProcess;
BEGIN
LOOP
WriteString(' in SubProcess');
TRANSFER(Process2,Process1);
WriteString(' back');
END;
END SubProcess;

BEGIN (* Main Module Body *)
NEWPROCESS(MainProcess,ADR(WorkSpace1),SIZE(WorkSpace1),
Process1);
NEWPROCESS(SubProcess,ADR(WorkSpace2),SIZE(WorkSpace2),
Process2);
TRANSFER(main,Process1);
WriteString('End of the program');
WriteLn;
END Corout2.

This program is identical with the last except that two lines are removed from the first process and placed in a normal procedure which is called from line 22 to illustrate that some of the code can be placed outside of the coroutine process to make the resident part smaller. The implication here is that you could have a four way coprocess going on, each one of which had a very small resident portion, and each one of which called some huge regular procedures. In fact, there is no reason why two or more of the coprocesses could not call the same normal procedure.

Study this program until you understand the implications, then compile and run it to see that it does exactly the same thing as the last program.

HOW ABOUT THREE PROCESSES?

Load and display COROUT3.MOD for an example with three concurrent processes.

COROUT3.MOD 

(* Chapter 15 - Program 3 *)
MODULE Corout3;

FROM InOut IMPORT WriteCard, WriteString, WriteLn;

FROM SYSTEM IMPORT WORD, PROCESS, ADR, SIZE,
NEWPROCESS, TRANSFER;

VAR main, Process1, Process2 ,Process3 : PROCESS;
WorkSpace1, WorkSpace2, WorkSpace3 : ARRAY[1..300] OF WORD;
Index : CARDINAL;

PROCEDURE MainProcess;
BEGIN
FOR Index := 1 TO 5 DO
WriteString('This is loop');
WriteCard(Index,2);
IF Index > 2 THEN
TRANSFER(Process1,Process2);
WriteString(' and back to main loop');
END;
WriteLn;
END;
WriteString('End of the MainProcess loop');
WriteLn;
TRANSFER(Process1,main);
END MainProcess;

PROCEDURE SubProcess;
BEGIN
LOOP
WriteString(' in SubProcess');
TRANSFER(Process2,Process3);
WriteString(' back');
END;
END SubProcess;

PROCEDURE ThirdProcess;
BEGIN
LOOP
WriteString(' in ThirdProcess');
TRANSFER(Process3,Process1);
WriteString(' back');
END;
END ThirdProcess;

BEGIN (* Main Module Body *)
NEWPROCESS(MainProcess,ADR(WorkSpace1),SIZE(WorkSpace1),
Process1);
NEWPROCESS(SubProcess,ADR(WorkSpace2),SIZE(WorkSpace2),
Process2);
NEWPROCESS(ThirdProcess, ADR(WorkSpace3),SIZE(WorkSpace3),
Process3);
TRANSFER(main,Process1);
WriteString('End of the program');
WriteLn;
END Corout3.

This program is identical to the first program with the addition of another process. In this program, process 1 calls process 2, which calls process 3, which once again calls process 1. Thus the same loop is traversed but with one additional stop along the way. It should be evident to you that there is no reason why this could not be extended to any number of processes each calling the next or in any looping scheme you could think up provided of course that it had some utilitarian purpose.

WHAT IS THE BIG DIFFERENCE?

Actually, the big difference between real concurrent processing and what this is doing is in the division of time and in who, or what, is doing the division. In real concurrent processing, the computer hardware is controlling the operation of the processing and allocating time slots to the various processes. In the pseudo concurrent processing we are doing, it is the processes themselves that are doing the time allocation leading to the possibility that if one of the processes went bad, it could tie up all of the resources of the system and no other process could do anything. You could consider it a challenge to write a successful system that really did share time and resources well.

The important thing to consider about this technique is that it is not a major breakthrough in a programming language, but one additional tool available in Modula-2 that is not available in the other popular languages such as Pascal, C, Fortran, or BASIC.

ONE MORE INFINITE EXAMPLE OF CONCURRENCY

Load and display the program named INFINITE.MOD for another example of a program with concurrency.

INFINITE.MOD 

(* Chapter 15 - Program 4 *)
MODULE Infinite; (* Infinite Coroutine loop *)

FROM InOut IMPORT WriteCard, WriteString, WriteLn;

FROM SYSTEM IMPORT WORD, PROCESS, ADR, SIZE,
NEWPROCESS, TRANSFER;

VAR main, Process1, Process2 ,Process3 : PROCESS;
WorkSpace1, WorkSpace2, WorkSpace3 : ARRAY[1..300] OF WORD;
Index : CARDINAL;

PROCEDURE MainProcess;
BEGIN
LOOP
WriteString('Main Process');
(* This may check the printer and send another character *)
(* for printing if it is ready. *)
TRANSFER(Process1,Process2);
WriteLn;
END;
END MainProcess;

PROCEDURE SubProcess;
BEGIN
LOOP
WriteString(' SubProcess');
(* This may check to see if there are any additional *)
(* characters to be read from the keyboard. *)
TRANSFER(Process2,Process3);
END;
END SubProcess;

PROCEDURE ThirdProcess;
BEGIN
LOOP
WriteString(' ThirdProcess');
(* This may check to see if there was another update in *)
(* the clock requiring service by the system. *)
TRANSFER(Process3,Process1);
END;
END ThirdProcess;

BEGIN (* Main Module Body *)
NEWPROCESS(MainProcess,ADR(WorkSpace1),SIZE(WorkSpace1),
Process1);
NEWPROCESS(SubProcess,ADR(WorkSpace2),SIZE(WorkSpace2),
Process2);
NEWPROCESS(ThirdProcess, ADR(WorkSpace3),SIZE(WorkSpace3),
Process3);
TRANSFER(main,Process1);
(* Note that we never return here, we stay in the status loop *)
END Infinite.

In this program, three processes are created and control is transferred to the first one after which they call each other in a loop with no provision for ever returning to the main program. The computer will continually loop around the three processes checking the printer, the keyboard, and the system clock to see if they need servicing. It must be pointed out that it would be a simple matter to include all three checks in a single loop in the main program and do away with all of this extra baggage. This method had one advantage over the simple loop and that is the fact that each coprocess can have its own variables which cannot be affected by the operation of the other processes and yet are all memory resident at all times.

This program can be compiled and run but it will execute forever since it has no way to stop it. You can stop it because it is writing to the monitor and therefore will be checking the control-break combination. Simply hit control-break and the program will be terminated by the operating system. It should be mentioned that if this technique were used in a real situation, it would probably not be writing to the monitor continually.

IS THIS REALLY USEFUL?

In a situation where you needed to service interrupts in some prescribed and rapid fashion, a technique involving Modula-2 concurrency could prove to be very useful. There are other procedures available in Modula-2 to aid in writing a pseudo-concurrent program but they are extrememly advanced and would require an initimate knowledge of your particular systems hardware, especially the interrupt system.

Since using these techniques are extremely advanced programming techniques, they will not be covered here. They are beyond the scope of this tutorial. It would be to your advantage to study them and know that they exist, then someday you may find that they fill the bill and greatly simplify some particular programming problem.

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT