Chapter 15 CONCURRENCY 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 few milliseconds 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. Concurrent programs must deal with asynchronous events that occur at arbitrary times. WHAT IS MODULA-2 CONCURRENCY? ______________________________________________________________ Most literature about Modula-2 uses the term concurrency to discuss the topic of two or more processes operating at the same apparent time, but the term coprocessing is more descriptive and will be used in this tutorial. Coprocessing can be defined as two or more asynchronous processes happening in such a way that they interact in some way, so that some level of coordination must be carried on between them. An example would be a text processor that is busy updating the video monitor between input keystrokes. 15-1 Chapter 15 - Concurrency Modula-2 provides the necessary constructs to implement coprocesses. We will define and illustrate most of them in this chapter but we will begin with a short example program as we have with all new topics. OUR FIRST COROUTINE ______________________________________________________________ Examine the program named COROUT.MOD for ================ our first look at this new technique. COROUT.MOD Without lots of explanation, there are ================ several new items imported and a few variables defined, the most interesting being the three variables declared that are of type PROCESS. These will be the three process variables we will use in this example, and we will say more about the process type variable later. There are two procedures defined in lines 13 through 36 which define what the processes will do. It must be noted that these procedures must be parameterless, no formal parameters allowed. Many of the details of these procedures will be explained in the next few paragraphs. Finally we come to the main program which is where we will start to define how to use the two processes. In the main program, we call the procedure NEWPROCESS which loads a new process without running it. We give it the name of the procedure where the process can be found, in this case MainProcess, 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 in the next two actual parameters. Finally, we give it the name of the process variable. Note that the workspace must be sufficient for the new process to store all of its variables, so give the process a generous workspace. In this case, we give the workspace 300 words. 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 with different formal parameters for the other process named SubProcess. WHAT DID NEWPROCESS DO? ______________________________________________________________ Each time we call NEWPROCESS, it loads a copy of the procedure named in the first actual parameter into the address space of the system at the location specified by the second actual parameter. It also stores a copy of the starting address of this procedure in the process variable named in the fourth actual parameter. The process variable is a record that can store the entire state of a process. We will see more about this record variable in the remainder of this chapter. 15-2 Chapter 15 - Concurrency The end result of execution of both procedure calls in lines 39 and 41 is that we have two procedures stored along with all of their local variables, in this case only the single variable defined in line 13, and we have two process variables stored. HOW DO WE GET IT STARTED? ______________________________________________________________ When we began executing the main program, we actually loaded and started a process, the one we are presently executing and this process stays resident in memory and active throughout the period of execution of the program. 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. To transfer control to a different process, we use the transfer function with the name of a process variable for the process we wish to terminate and another process variable for the process we wish to start. The current state of the hardware processor is stored in the first process variable mentioned, and the state of the second process variable named is loaded into the hardware processor. This effectively transfers control to the second process. 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. The transfer procedure in line 19 stores the current state of the hardware processor in the process variable named Process1 and retrieves the machine state from Process2, stores it in the hardware processor, and jumps to the new process. The jump is effected automatically since the instruction counter is part of the process variable. Thus we jump from line 19 in the first procedure to line 31 in the second where we begin an infinite loop. We display another string in line 32 and once again execute another transfer from line 33 to somewhere. This transfer causes the current state of the hardware processor to be stored in Process2 and retrieves the state from process1 and stores it in the hardware processor. Since the state stored in the process variable named Process1 was stored there at statement 19, the address of the next line is a part of this state and the program counter is automatically set to the address where line 20 is stored and execution is returned there. WHERE DO WE RETURN TO A COROUTINE? ______________________________________________________________ Instead of jumping to the beginning of the procedure named MainProcess again, which would be line 15 in this example, we return to the statement following the one we transferred away 15-3 Chapter 15 - Concurrency 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 the state stored in 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 MainProcess finishes, we execute lines 24 and 25 in normal sequential fashion, then the transfer in line 26 will load the data stored in the process variable named main effectively returning control to the main module body where we will arrive at line 44. From there we complete the last two write statements, then do a normal exit to the operating system. This example of coprocess usage was defined step by step as a beginning into this topic. If it was not clear, reread the entire description until you understand it. There is really nothing else to know about coroutines other than the 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 GOOD FOR? ______________________________________________________________ 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 along with their local variables. This is not true of normal procedures. It is transparent to you, but variables local to a procedure are not all simply loaded into the computer and left there. They are dynamically allocated and used each time the procedure is called, then discarded when the procedure completes its job. That is why variables are not saved from one invocation of a procedure to the next. The variables which are defined in the outer layer of a process are loaded once, and stay resident for the life of the process. In the example program under consideration, all three processes including the main program are loaded and none is really the main program since any of the programs have the ability to call or control the others. Rather than the strict hierarchy of calling, as is done with procedures, the coroutines are all at the same hierarchical level. Examine the program named COROUT2.MOD for =============== the second example with coroutines. This COROUT2.MOD program is identical with the last one =============== 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 several 15-4 Chapter 15 - Concurrency coprocesses going on, each of which had a very small resident portion, and each of which called some large 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 execute it to see that it does exactly the same thing as the last program. HOW ABOUT THREE PROCESSES? ______________________________________________________________ Examine the example program named =============== COROUT3.MOD for an example with three COROUT3.MOD concurrent processes. This program is =============== identical to the first program with the addition of another process. In this program, Process1 calls Process2, which calls Process3, which once again calls Process1. 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. It is not apparent, but there are actually four coprocesses here because the main program, in lines 47-57, qualifies to be called another process. This will be very graphically demonstrated in the next example program because the main program is the main control loop. SORT OF RANDOM PROCESSES ______________________________________________________________ Examine the program named RANPROG.MOD for =============== an example of coprocessing that is a RANPROG.MOD little more irregular than the very =============== repeatable loops of the first three example programs in this chapter. Note that the main program is one of the major contributors to this program because it contains a loop which calls each of the other processes once during each pass through the loop, with WriteString calls interleaved between the calls. The WriteString calls are obviously for the purpose of tracking program execution later. Each call to Hello results in writing the word "hello" to the monitor and a return to the main program, but each call to Toggle results in either a return to the main program or a transfer to Hello after writing a different word to the monitor in each case. Which path is taken is controlled by the local variable ToggleFactor which is toggled each time the Toggle procedure is called. If ToggleFactor is zero, the word "even" is written to the monitor, and control is transferred to Hello, which writes "hello" and transfers directly back to 15-5 Chapter 15 - Concurrency the main program without returning through the Toggle procedure. When ToggleFactor is one, the word "odd" is displayed and control is transferred back to the main program. The observant student will notice that we return to each process at the statement following where we transferred away and in the case of the procedure named toggle, we actually return inside of a loop and inside of an if statement. The Modula-2 system is designed to handle this for us, and it is because of this capability that we can write true coprocessing programs in Modula-2. The time it takes you to understand this program will be well spent since it is the only program in this tutorial that depicts the value of the concurrent capabilities of Modula-2. The interested student will find many books and articles on coprocessing and parallel operation in general. 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 one additional tool available in Modula-2 that is not available in the usual implementations of other popular languages such as Pascal, C, Fortran, or BASIC. ONE MORE INFINITE EXAMPLE OF CONCURRENCY ______________________________________________________________ Examine the program named INFINITE.MOD for ================ another example of a program with INFINITE.MOD concurrency. 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 15-6 Chapter 15 - Concurrency 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 must stop it yourself by using the method of program abort provided on your system. If you are using MS-DOS, a control-break combination will probably terminate the program. It should be mentioned that if this technique were used in a real situation, it would 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 extremely advanced and would require an intimate knowledge of your particular systems hardware, especially the interrupt system. The one procedure available for this is the IOTRANSFER procedure which is used to establish the interface to a hardware interrupt. Since using these techniques are extremely advanced programming techniques, they are beyond the scope of this tutorial and will not be covered here. WHAT ABOUT USING THESE PROCEDURES ______________________________________________________________ In order to effectively use the techniques of coprocessing, you will need to devote a great deal of study to parallel processing. Many terms will be encountered, such as shared variables, semaphores, deadlock, etc. Each of these terms involve techniques on programming problems that are beyond the scope of this tutorial since our goal is to teach you the use of various constructs in Modula-2, not the overall system design problem. Chapter 16 of this tutorial gives you practical examples of the use of Modula-2. 15-7