Concurrency and Shreds
ChucK is able to run many processes concurrently (the process behave as though they were running in parallel). A ChucKian process is called a "shred". "Sporking" a shred means creating and adding a new process to the virtual machine. Shreds may be sporked from a variety of places, and may themselves spork new shreds.
Central to ChucK's timing and concurrency is the "shreduler", a part of the virtual machine that keeps track of what processes are intended (or "shreduled") to run at any geven time. It does so to a very high precision, much more accurate than the sample rate. This is not just because things need to happen at the right time; they also need to happen in the right order. In ChucK the order in which commands are executed is always deterministic; when several commands are shreduled to happen at the same time they will be dealt with in the order in which they were shreduled. Note that any given process/shred does not necessarily need to know about any other - it only has to deal with time locally. The virtual machine will make sure things happen correctly ”across the board”.
The simplest way to to run shreds concurrently is to specify them on the command line:
%>chuck foo.ck bar.ck boo.ck
The above command tells chuck to run foo.ck, bar.ck, and boo.ck concurrently. There are other ways to run shreds concurrently (see on-the-fly programming commands). Next, we show how to create new shreds from within ChucK programs.
Sporking Shreds (In Code)
To spork means to shredule a new shred.
To spork a shred, use the spork keyword/operator:
- Spork dynamically turns functions into processes that run parallel to the shred that sporked them
- This new shred is shreduled to execute immediately
- The parent shred continues to execute, until time is advanced (see manipulating time) or until the parent explicitly yields (see next section).
- In the current implementation, when a parent shred exits, all child shreds all exit (this behavior will be enhanced in the future.)
- Sporking a function returns a reference to the new shred, note that this operation does not return a function's return value, currently only functions of type void can be sporked - the ability to get back the return value at some later point in time will be provided in a future release.
// define function go() fun void go() { // insert code } // spork a new shred to start running from go() spork ~ go(); // spork another, store reference to new shred in offspring spork ~ go() => Shred @ offspring;
A slightly longer example:
// define function fun void foo( string s ) { // infinite time loop while( true ) { // print s <<< s >>>; // advance time 500::ms => now; } } // spork shred, passing in "you" as argument to foo spork ~ foo( "you" ); // advance time by 250 ms 250::ms => now; // spork another shred spork ~ foo( "me" ); // infinite time loop - to keep child shreds around while( true ) 1::second => now;
also see the function section for more information on working with functions.
The ’me’ keyword
The me keyword (type Shred) refers the current shred.
Sometimes it is useful to suspend the current shred without advancing time, and give other shreds shreduled for the current time a chance to execute. me.yield() does exactly that. This is often useful immediately after sporking a new shred, when you would like for the new shred to have a chance to run but you do not want to advance time yet for yourself.
// spork shred spork ~ go(); // suspend the current shred ... // ... give other shreds (shreduled for ’now’) a chance to run me.yield();
The me keyword is also useful to exit the current shred. For example if a MIDI device fails to open, you may exit the current shred.
// make a MidiIn object MidiIn min; // try to open device 0 (chuck --probe to list all device) if( !min.open( 0 ) ) { // print error message <<< "can’t open MIDI device" >>>; // exit the current shred me.exit(); }
Finally it can be used to get the shred id:
// print out the shred id <<< me.id(); >>>;
These functions are common to all shreds, but note that yield() can only be used from within the current shred.
using Machine.add()
Machine.add( string path ) takes the path to a chuck program, and sporks it. Unlike with the spork command, there is no parent-child relationship between the shred that calls the function and the new shred that is added. This is useful for dynamically running stored programs.
// spork "foo.ck" Machine.add( "foo.ck" );
Presently, this returns the id of the new shred, not a reference to the shred. This will likely be changed in the future.
Similarly, you can remove shreds from the virtual machine.
// add Machine.add( "foo.ck" ) => int id; // remove shred with id Machine.remove( id ); // add Machine.add( "boo.ck" ) => id // replace shred with "bar.ck" Machine.replace( id, "bar.ck" );
Inter-Shred Communication
Shreds sporked in the same file can share the same global variables. They can use time and events to synchronize to each other (see Events). Shreds sporked from different files can share data (including events). For now, this is done through a public class with static data (see Classes). Static data is not completely implemented, We will fix this very soon!