Parrot Virtual Machine/Multithreading and Concurrency
Multithreading is an area where interpreted languages such as Perl 5 have had some problems in the past. Perl 5, as a prime example, had a very buggy and non-robust implementation of threads that was not scalable.
In contrast to Perl 5, Perl 6 is including advanced multithreading and concurrency features in the core language. To support all these advanced features of the Perl 6 language, Parrot must provide those same features as well.
While not technically "multithreading", coroutines represent a simple form of concurrency that will find many interesting uses as they catch on with mainstream programmers. Coroutines are like subroutines which use a yield statement instead of a return statement. The
.yield statement causes the coroutine to return a value to the caller. However,
.yield does not cause the coroutine to exit, disappear, or lose its current state. The next time the caller calls the coroutine, the coroutine will pick up where it left off the last time. Here is an example:
.sub 'MyCoroutine' .yield(1) .yield(2) .return(3) .end
.sub 'MyCaller' $I0 = 'MyCoroutine'() say($I0) $I0 = 'MyCoroutine'() say($I0) $I0 = 'MyCoroutine'() say($I0) .return
The output of the function
MyCaller will be:
1 2 3
Coroutines are immediately useful in places where a persistent state needs to be maintained, but where it might be difficult to coordinate that state between multiple callers, or multiple threads. One such example is in file or database access where a coroutine can serialize accesses, and maintain persistent state information about the size of the file and locations of certain features in the file.
Coroutines are also called "continuations" in other places, or even "continuation sandwich" in others.
Parameters to a coroutine are only passed the first time the coroutine is called, or any time the coroutine is called after having a
.return statement. Here is an example:
.sub 'MyCoroutine' .param int a .yield(a) .yield(a) .yield(a) .return(a) .end .sub 'main' :main $I0 = 'MyCoroutine'(1) # the "call" say $I0 $I0 = 'MyCoroutine'(2) # "continuation" say $I0 $I0 = 'MyCoroutine'(3) # "continuation" say $I0 $I0 = 'MyCoroutine'(4) # "continuation" say $I0 .end
This code will print the following result, even though the parameter to the
MyCoroutine function changes with each call:
1 1 1 1
This is because only the first call to
MyCoroutine is actually a function call that creates the local parameters. The other calls to
MyCoroutine are simply continuations, not calls. A continuation does not create new parameter variables.
Returning and Yielding
A return call in a coroutine causes it to return a value to the caller and to destroy it's current lexical scope, as usual. A yield call, however, will return a value without destroying the current lexical scope. Yielding then will allow the coroutine to maintain it's current state and resume it's execution the next time it is called.
Continuation and Coroutine PMCs
This is where the heart of the coroutine system is located, the coroutine PMC. The coroutine PMC is an object that stores the state of the coroutine so that it can be called multiple times in a row. A continuation PMC, which is a superclass of the coroutine, is a stored interpreter state. The coroutine operates by storing a continuation when a
.yield directive is called, and invoking that continuation when the coroutine is called again.
||Threading in Parrot is not currently implemented, although it is being worked on by developers actively. Material in this section is mostly speculation based on the Parrot Design Documents. Once concurrency is implemented officially, this information will all be updated.|
Internally, Parrot supports multiple different methods to perform threading. Luckily, all of these different methods are abstracted and the details are hidden from the HLL programmer. Parrot's concurrency system is modular, so new multithreading technologies can be added to Parrot later, and HLL programmers will be able to benefit from these changes and additions without having to make any changes to their code.
There are four basic operations that can be used to create and manage a new thread. In Parrot, threads are all abstracted in the Task PMC. To create a new thread, you first create a new Task PMC, and then you add it to the scheduler.