A Counter Object

This section shows a simple example of two threads sending a message. One thread will implement a counter object that increments its value in response to commands. For the body of the object I use this counter function.

datatype Message = 
        MsgIsIncr of int
    |   MsgIsStop

fun counter in_chan () =
let
    fun loop count =
    (
        case CML.recv in_chan of
          MsgIsIncr n => loop (count + n)

        | MsgIsStop =>
        (
            print(concat["Count is ", Int.toString count, "\n"])
        )
    )
in
    loop 0
end

The possible messages are defined by the Message datatype. The counter function contains a loop implementing a state machine. The state variable is the count with an initial value of 0. When the counter is stopped it just prints its count. The channel is passed in via the in_chan argument when the function is started.

The client of the object is a thread running this driver function which sends some messages and stops.

fun driver out_chan () =
let
in
    CML.send(out_chan, MsgIsIncr 3);
    CML.send(out_chan, MsgIsIncr ~1);
    CML.send(out_chan, MsgIsStop)
end

The main thread of the program is this function called run.

fun run() =
let
    val chan: Message CML.chan = CML.channel()
    val d = CML.spawn (driver chan)
    val c = CML.spawn (counter chan)
in
    CML.sync(CML.joinEvt d);
    CML.sync(CML.joinEvt c);
    ()
end

I first create a channel which passes Message values. I've used an explicit type constraint to make this clear but type inference would have figured it out if I didn't. Then I spawn two threads for the counter and its driver. The channel is passed to each thread via the curried arguments in_chan or out_chan. (Because of the () argument in the function definitions, the expression (driver chan) is a function taking unit as its argument, which matches the requirement of the CML.spawn function.) Then I wait for each thread to terminate. The joinEvt function returns an event that is enabled when the thread terminates. The call to sync performs the wait.

A thread terminates when its function returns or it explicitly calls CML.exit. Thread functions always return unit and so does the join event so there is no mechanism for a thread to return a value when it terminates unless it sends one through a channel (or does something ugly like write to a global variable).

I don't actually need to do the wait in the run function. If I don't then the function will return and its thread will terminate but the spawned threads will continue. The program won't terminate until all of its threads have terminated.

Here is the main function. To start the main thread I need to explicitly call RunCML.doit which is a (currently) undocumented function. The second argument is an optional time interval for scheduling which defaults to 20 milliseconds.

fun main(arg0, argv) =
let
in
    RunCML.doit(run, NONE);
    OS.Process.success
end

The CM file for this program is as follows. It picks up the CML package containing the CML, SyncVar, Mailbox, and modified I/O structures.

group is 
    counter.sml
    /src/smlnj/current/lib/cml.cm
    /src/smlnj/current/lib/cml-lib.cm

You can terminate the program early by calling RunCML.shutdown with an exit status. This will cause the RunCML.doit function to return early with the status value. For example

RunCML.shutdown OS.Process.failure

In this case you would call RunCML.shutdown with a success status for normal exit. For example

fun run() =
let
    ...
in
    ...
    RunCML.shutdown OS.Process.success
end

fun main(arg0, argv) =
(
    RunCML.doit(run, NONE)
)