User Tools

Site Tools


ugbasic:user:multitasking
Translations of this page:


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

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

  SPAWN example

Deciding the right moment

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

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.