VII: Doing Two Things At Once




Threads and Concurrency



This is a short chapter dedicated to covering ways to run more than one piece of code at once. Here we will cover the threads module, introduced in Centum 0.0.9 and modified for 0.1.0 as well as coroutines which are implemented in the module frozenfunctions, and finally the label: syntax introduced in version 0.1.2. Normally multithreading, the ability to run more than one piece of code at the same time within a program is hard at best. However Centum has hides most of the problems from you. There are only three threading functions covered within this chapter: Sleep, NewThread and Concurrent; all of which are part of threads.txt.


    Sleep

The Sleep function is the simplest of the three. It simply pauses the current thread for a number of milliseconds, the exact number being the parameter passed to it. By the way, in this chapter each block of code that is executing simultaneously will be called a thread. All the programs you have seen so far have had a single thread, the main body of the program. Now with that out of the way, an example.


    Sleepy


#include threads.txt
#include console.txt

_INIT
	{
	PrintLn "Wait for it ..."
	Sleep 1000
	PrintLn "... done"
	}



    NewThread

The NewThread function causes another block of code to be executed at the same time as all other operations within the program. It is passed any number of parameters, with the last being a function containing that code to execute. That function is then immediately called (in the global scope) with any parameters that were passed to the NewThread function. Note that the program will not finished until all threads have also finished, so make sure that your threads do not enter infinite loops.


    Spawning threads


#include basics.txt
#include threads.txt
#include console.txt

run is true

_INIT
	{
	MakePrintingThread "thread 1"
	MakePrintingThread "thread 2"

	Sleep 3000
	run is false
	}

MakePrintingThread
	{
	NewThread p_0
	    {
	    my word is p_0
	    while {return run}
		{
		PrintLn word
		Sleep 500
		}
	    PrintLn "Printing Thread Close"
	    }
	}



    Concurrent

The final thread function is Concurrent. Concurrent takes any number of functions as parameters. It executes all of those functions simultaneously, and does not itself return until all the treads it has created are finished. Think of it as branching the thread it is called in into several parts which rejoin when it is finished. Threads created in this way are called with the function Concurrent was called in as their caller, which means that you can access static, local, and the my variables of that function.


    Two for the price of one


#include threads.txt
#include basics.txt
#include console.txt

_INIT
	{
	PrintLn "begin program"
	
	my str is "a variable"

	Concurrent
	    {
	    loop 4
		{
		PrintLn str
		Sleep 500
		}
	    }
	    {
	    loop 6
		{
		PrintLn "at the same time ..."
		Sleep 300
		}
	    }

	PrintLn "end program"
	}



Coroutines



Coroutines are called Frozen Functions internally here at Centum. Why? Because I think it sounds cooler, so there. For those of you who have not been exposed to coroutines they function like this: First a new coroutine is created based on an existing function (or a function instance in Centum). Then when it is needed the coroutine is called, and it executes the code within the function until the function returns. This is basically the same as a normal function call, except that if the coroutine is called again execution of the function begins from where it left off, not at the beginning of the function as with a normal function call. One use of coroutines is to make “interesting” counters for loops.


    frozenfunctions.txt

The following functions are found in frozenfunctions.txt

Freeze: Passed a rebi (either an instance or a pure definition) and any numbers of parameters and/or usings. It returns a FrozenFunction “scope” based on the first parameter with the remaining parameters and usings being treated as that scopes own parameters and usings. This scope's caller is the global scope. If you call the rebi retuned by Freeze it is the same as CallFrozen (below)

CallFrozen: Executes the statements in a “scope” created with Freeze until a return or exception is thrown, which is then passed to the caller of the CallFrozen function. If the function is at the end of its instructions when CallFrozen is called upon it will return with no values. (see Reset for how to avoid this).

SetFreezePosition:Sets the next instruction to be executed in a frozen function (the first parameter) to be the nth instruction, where n is given by the second parameter. The number of a given instruction is related to the compiled version of the function and has no relation at all to the text version of the function. SetFreezePostition and GetFreezePosition below exist for purely academic purposes and I strongly recommend against using them unless you absolutely know what you are doing.

GetFreezePosition: Returns the number of the next instruction to be executed if the frozen function (the first parameter) were to be called with call frozen. Position 0 is the beginning of the function.

ResetFrozen: Resets the current position of the frozen function (1st parameter), as if that function had encountered a Reset during its execution (see below). If an exception has been returned from a CallFrozen statement that was not generated by a throw statement in the main body of the function you may need to call ResetFrozen on that function to prevent unexpected results upon executing the function again.

Reset: Causes the next instruction to be executed in a function to be the first line of the function (it resets execution). This is an easy way to trap yourself in an infinite loop, so remember to put a return before it. This can prevent a frozenfunction from being stuck at the end of its instructions forever. See the example for why this is useful.

Trap: Trap is passed 1 function as argument. Calls that function, turns any multiple returns or exceptions into normal returns. Trap is useful in a situation like this:
initializations
Trap
{
function body
}
mandatory cleanup

The Trap prevents the mandatory cleanup from being skipped under any circumstances. Trap is also useful in this case:
Trap
	{
	code that may throw exceptions
	catch typeA
		{ ... }
	catch typeB
		{ ... }
	}
In this case the Trap statement prevents uncaught exceptions from propagating outward.


    A Fibonacci example

This example shows how to create a Fibonacci series counter using a frozen function. To reset the counter you need to create an new instance of the function. Frozen functions and rebi instances go hand in hand, because you often need variable that persist through the entire lifetime of the frozen function, but are unique to that one freezing of it. You may have suggested my variables, but my type variables are reset to undef whenever they are initialized, and thus a frozen function that covered the same area of code multiple times.


    Unusual counting


#include frozenfunctions.txt
#include console.txt
#include numbers.txt

FibCounter
	{
	local old is 1
	local new is 1
	
	return old
	old new are new (+ old new)
	Reset
	}

_INIT
	{
	PrintLn "A unique counter"
	my counter is (Freeze (A FibCounter))
	
	loop 10
		{
		PrintLn "Term: " p_0 ": " (counter)
		}
	}



    The Select function

You may be asking yourself "why is Peter covering the Select function found in basics.txt in chapter 7?" The answer is simple ... TINY DEAMONS LIVING IN MY EYES!! Anyways the Select function takes any number of arguments followed by a function. That function is called upon every other argument passed to the Select function, and all arguments that the function returns true for are returned by select. For example to examine all input except for strings of “?” you could write Select params { not (strcmp “?” p_0) }. OK, now I’m done.


The label: Syntax



The label: syntax (introduced in 0.1.2) works thus, in any function you may place the code label: any_name preferably at the beginning. When a called function runs across this code it tags itself with that string name. A function may only have one label at a time. You may use that label as if it were a variable storing this for the function, allowing recursion simply. For example:
function 
   { 
   label: the_function 
   ... 
   the_function values to recuse with 
   ... 
   }

you may also use it with a :params, :usings, :p_0, or any other legal way of using the : notion to access its parameters and my variables from anywhere after it has been labeled. If more than one function share the same label (for example the recursive function that is labeled each time) only the most recent label can be accessed (the other labels are not lost, just temporarily inaccessible).


    New ways of returning

You may also use the functions returnto/ReturnTo and returnfrom/ReturnFrom followed by the name of a label and then any return values to return either to a function with that label of from one, as appropriate, remember only the most shallow function with that label is found. These functions are defined in labels.txt.


    Stack tracing

Finally the StackTrace function returns the labels of the scopes that have led to the current scope, starting with the current. These labels are returned as String values, and unknown labels are returned as "??". Remember that the anonymous functions passed to control statements as well as the global scope itself count as scopes, so don't be surprised by a good number of "??" strings returned. This function is also defined in labels.txt.


    Some labels and recursion


#include basics.txt
#include labels.txt
#include console.txt

finalfunc
	{
	label: final

	PrintLn "final function"
	PrintLn "trace: " (Intersperse "<-" (StackTrace))

	# note we can access my type variables
	PrintLn "data is " data
	PrintLn "_INIT:data is " _INIT:data
	PrintLn

	returnto _INIT
	}

recursive
	{
	params: count

	label: recursive
	my data is "recursive's String"
	
	PrintLn "recusrive called, count: " count

	# note how the trace accumulates all the recursive labels
	PrintLn "trace: " (Intersperse "<-" (StackTrace))
	PrintLn
	
	if ( < count 1 )
		{
		finalfunc
		}
	else
		{
		recursive (- count 1)
		}

	# this statement is never reached due to returnto _INIT in finalfunc
	PrintLn "End of recursive"
	}

_INIT
	{
	label: _INIT
	my data is "_INIT's String"
	
	recursive 3
	
	# returnto goes to here, skipping the trailing bits of all
	# of the recursive functions (very handy sometimes)
	PrintLn "end of _INIT function"
	}

@
Note: here is a way to use returnto to bypass a recursice stack

recursivewrapper
	{
	label: mywrapper
	recursive params
	}

recursive
	{
	...
	returnto mywrapper
	...
	}
@



Go forth and multithread



That about wraps up multithreading and coroutines. Remember, the threads in version 0.1 and later work slightly different from those in 0.0.9 and earlier, so make sure to update your install. And now onto the long awaited chapter VIII.