In this section you will learn how you can write multitasking programs using parallel programming, available “out of the box” on ugBASIC. If you want to deepen the theory behind it, I recommend reading this article.
In computer science, multitasking means the support in the execution of multiple flows of execution at the same time (in a “parallel” way). In systems with severe memory constraints such as computers based on 8 bit processors, this cannot be obtained using traditional techniques, because it takes too much memory since each parallel process requires its own stack which, in turn, occupies large parts of the available memory.
For this reason a mechanism based on protothreads has been implemented. This mechanism makes it possible to define procedures that will be executed for appropriate “quanta” of time, without however the execution being interrupted unexpectedly.
In ugBASIC the
PROCEDUREs are stack less. This means that there is no stack for local variables, but they are mapped (trasparently) into the memory. This means that standard
PROCEDUREs are ready to be promoted for a parallel approach. And in fact converting a sequential procedure into a parallel one is very simple: just add the
PARALLEL keyword where you use the
For example, this procedure will print forever the string “example” on the screen:
PROCEDURE example1 DO PRINT "example" LOOP END PROC
So you can call it using the standard syntax:
If you want to convert this routine in a procedure that can be run in a multitasking fashion, you have to use the
PARALLEL keyword, like that:
PARALLEL PROCEDURE example DO PRINT "example" LOOP END PROC
When this keyword is added, the procedure can no longer be called directly from the program but the keyword
SPAWN must be used. So:
Now let's try to write down the following example program, and run it.
PARALLEL PROCEDURE example DO PRINT "example" LOOP END PROC SPAWN example
If you compile the source code with ugBASIC v1.14.1 or less, you can notice that, on the screen, you will see nothing. Why? Because the
SPAWN command will prepare only the system to run the procedure in a parallel fashion. You need to decide when this will occour in your main program.
The command to use to invoke a “multitasking slice” is
RUN PARALLEL. Whenever that command is invoked, all the procedures registered to run in parallel mode are executed in the same “slice” of time. In order to make “permanent” execution of the various programs in parallel, you need to place the call inside a
For example, this is a very simple example. We will run two separates tasks: the first will print the “example” string, while the second will print the “example2”. Since the two tasks are running at the very same time on the processor, the two strings are printed alternatively.
PARALLEL PROCEDURE example1 DO PRINT "example" LOOP END PROC PARALLEL PROCEDURE example2 DO PRINT "example2" LOOP END PROC SPAWN example1 SPAWN example2 DO RUN PARALLEL LOOP
If you use the version 1.14.2 or better and you do not use
RUN PARALLEL, the last three lines will be not needed, since they will be generated automatically at the very end of the program.
Now let's introduce a way to synchronize the execution of two tasks. In particular, this is the way to invoke a task inside another task, and how to wait the end of execution of the inner task before continue.
PARALLEL PROCEDURE example1 FOR x = 1 TO 10 PRINT "example ";x NEXT END PROC PARALLEL PROCEDURE example2 WAIT PARALLEL SPAWN example1 PRINT "example2" END PROC SPAWN example2 DO RUN PARALLEL LOOP
As you can easily verify by running this program, the second procedure will execute only when the first has finished. In order to wait the execution, we will use the
WAIT PARALLEL instruction. This command will take the identification of the current task (“task id”), that is the valure returned by the
As we said earlier, procedures do not have an independent stack. This means that it is not possible to use local variables, at least not directly. More precisely, each variable is shared among all tasks (of the same type).
PARALLEL PROCEDURE example DO x = x + 1 PRINT x;" "; LOOP END PROC
In this example, the
x variable is shared among all invoked tasks. So, if you invoke 5 tasks, the value of
x will be increased monotonously as well as if you invoke just one task.
To overcome this problem you can use just a global array (
x(…)), the size of which will be equal to the number of tasks concurrently in execution. Each element of the
x array will store the variable value for that specific task. To find out what is the number of the task being executed is, you can use the keyword
Let's look at this example:
DIM x WITH 0 (3) GLOBAL x PARALLEL PROCEDURE example DO x(TASK) = x(TASK) + 1 PRINT x(TASK);" "; LOOP END PROC SPAWN example SPAWN example SPAWN example
When running the example, you will see that the value of
x will be incremented, separately, for each task.
Finally, you can use a shorter form by enclosing the variable name inside square brackets:
' [a] -> a(TASK) PARALLEL PROCEDURE example DO [x] = [x] + 1 PRINT [x];" "; LOOP END PROC
So far we have seen how it is sufficient to define a procedure with the
PARALLEL keyword to see it execute in parallel. However, this is possible because ugBASIC implicitly generates the commands needed to coordinate the various tasks. So, you can force the early release of the time slice by using the
For the same reason, it is not possible to busy wait for a certain condition to be true or false, because this would prevent ugBASIC from generating the appropriate escape routes.
For this purpose, two commands have been introduced:
WAIT UNTIL [condition]
WAIT WHILE [condition]
These two commands ensure that the execution of the single task stops while the rest of the tasks proceed.