ugBASIC User Manual

Multitasking

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.

Introduction Define Run Synchronization Variables Wait

Introduction

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.

Defining parallel procedures

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 PROCEDURE keyword.

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:

example1[]

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 example1
   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:

SPAWN example

Deciding the right moment

Now let's try to write down the following example program, and run it.

PARALLEL PROCEDURE example1
   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 DO...LOOP.

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.

Simple synchronization

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 SPAWN command.

Using (local) variables

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 TASK (or THREAD).

Let's look at this example:

DIM x(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

Forcing slice release

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 YIELD command.

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.

Any problem?

If you have found a problem, if you think there is a bug or, more simply, you would like something to be improved, write a topic on the official forum, or open an issue on GitHub.

Thank you!