News

muvee Reveal - the latest incarnation of muvee's flagship product - has just been released on 11 June 2008! The behaviours of the 8 bundled styles are specified using muSE, in addition to all the styles developed for the now discontinued muvee autoProducer 6.1.

Wednesday, December 13, 2006

Erlang style processes in muSE

muSE now has an implementation of co-operative message passing processes in the same spirit as Erlang. The source code canbe obtained from the processes branch.

muSE processes provide an abstraction that let you think of your program as concurrently acting entities without worrying about the actual order in which the operations are actually being performed by the processor. Evaluation of muSE expressions may be pre-empted at graph reduction boundaries to pay some attention to other processes. Switching between processes is fairly efficient (close to setjmp + longjmp in C) and it is even possible to run 10000 processes without bringing down your machine to its knees, granted that each process will run pretty slowly on the average in that case though.

Spawning processes

(spawn thunk [attention])

Spawns off the given thunk (a function that takes no parameters) into a separate process. The thunk is evaluated in a loop, until it returns a non-nil value. Once the thunk returns with a non-nil value, the process dies. This is thunk's way of saying "I'm done." The (optional) second argument to spawn is an attention value that tells the muSE scheduler how much attention it should give to the created process before switching to another. The default value is 10. Play around until you find something that suits you. The processes are all scheduled in a simple round-robin manner for now.

The spawn expression itself evaluates to the process-id of the created process. The process id is not a number as is usual in most systems, but is actually a native-closure. The only two uses for the pid are to compare two pids for equality and to pass messages to the process it identifies.

To pass a message, simply use the process ID like a normal function. Its entire argument list will be placed as a single message in the process's message queue.

Pausing a process

(run [timeout-microseconds])

Pauses a process for the given timeout period, yielding time to other processes. If the timeout is omitted, the process is suspended for ever.

Receiving messages in a process

(receive ...)

Retrieves the next message in the process's mailbox. The message has the format
(pid . values)
where pid is the id of the process that sent the message and values is the list of arguments that it supplied to the message sending operation.

The result of a (receive...) expression is designed to be used with muSE's pattern-matching case construct as follows -

(case (receive)
((pid ...msg-pattern-1...) action-1)
((pid ...msg-pattern-2...) action-2)
...)

The receive primitive has four forms -
(receive)
Pauses the process until a message is available in the process's mailbox and evaluates to the message. This will always evaluate to a valid message.

(receive timeout-microseconds)
Pauses the process for at most timeout-microseconds. If there is mo message in the process's mailbox for more than timeout-microseconds, it evaluates to () which is muSE's false value.

(receive pid)
Waits for and retrieves the next message from the process with the given pid.

(receive pid timeout-microseconds)
Similar to the previous one, but times out with a () value after timeout-microseconds.

Controlling concurrency

(atomic ...expressions...)

From the point of view of a single process, atomic works exactly like do - evaluating all the expression in turn and itself evaluating to the value of the last expression in the series. From the point of view of the cluster of running processes, it does not yield any time to any other process until all its expressions are evaluated.

atomic expressions may be nested to arbitrary depths. You can forcibly yield to other processes however, if you use any of the pausing functions receive and run.

Process identity

(this-process)

Evaluates to the pid of the process in which it is evaluated.

Embedding issues

For muSE, it is important to consider how these processes interact when embedded in a C/C++ based application. The application can make calls to the muSE API to evaluate expressions. These calls are always evaluated in the "main process" using the main application's C stack. Each muSE process has its own C stack and won't interfere with the main C stack. The entry and exit processes for this case will always be the main process. Other processes get a chance to execute only when within a muSE API call and therefore will not interfere with the application's execution when not running muSE code.

No comments: