Processes and Channels
Factor handbook ยป Guided tour of Factor

Prev:Servers and Furnace
Next:Where to go from here?


As discussed earlier, Factor is single-threaded from the point of view of the OS. If we want to make use of multiple cores, we need a way to spawn Factor processes and communicate between them. Factor implements two different models of message-passing concurrency: the actor model, which is based on the idea of sending messages asynchronously between threads, and the CSP model, based on the use of channels.

As a warm-up, we will make a simple example of communication between threads in the same process.
FROM: concurrency.messaging => send receive ;

We can start a thread that will receive a message and print it repeatedly:
: print-repeatedly ( -- ) receive . print-repeatedly ; [ print-repeatedly ] "printer" spawn

A thread whose quotation starts with receive and calls itself recursively behaves like an actor in Erlang or Akka. We can then use send to send messages to it. Try "hello" over send and then "threading" over send.

Channels are slightly different abstractions, used for instance in Go and in Clojure core.async. They decouple the sender and the receiver, and are usually used synchronously. For instance, one side can receive from a channel before some other party sends something to it. This just means that the receiving end yields control to the scheduler, which waits for a message to be sent before giving control to the receiver again. This feature sometimes makes it easier to synchronize multithreaded applications.

Again, we first use a channel to communicate between threads in the same process. As expected, USE: channels. You can create a channel with <channel>, write to it with to and read from it with from. Note that both operations are blocking: to will block until the value is read in a different thread, and from will block until a value is available.

We create a channel and give it a name with
SYMBOL: ch <channel> ch set

Then we write to it in a separate thread, in order not to block the UI
[ "hello" ch get to ] in-thread

We can then read the value in the UI with
ch get from

We can also invert the order:
[ ch get from . ] in-thread "hello" ch get to

This works fine, since we had set the reader first.

Now, for the interesting part: we will start a second Factor instance and communicate via message sending. Factor transparently supports sending messages over the network, serializing values with the serialize vocabulary.

Start another instance of Factor, and run a node server on it. We will use the word <inet4>, that creates an IPv4 address from a host and a port, and the <node-server> constructor
USE: concurrency.distributed f 9000 <inet4> <node-server> start-server

Here we have used f as host, which just stands for localhost. We will also start a thread that keeps a running count of the numbers it has received.
FROM: concurrency.messaging => send receive ; : add ( x -- y ) receive + dup . add ; [ 0 add ] "adder" spawn

Once we have started the server, we can make a thread available with register-remote-thread:
dup name>> register-remote-thread

Now we switch to the other instance of Factor. Here we will receive a reference to the remote thread and start sending numbers to it. The address of a thread is just the address of its server and the name we have registered the thread with, so we obtain a reference to our adder thread with
f 9000 <inet4> "adder" <remote-thread>

Now, we reimport send just to be sure (there is an overlap with a word having the same name in io.sockets, that we have imported)
FROM: concurrency.messaging => send receive ;

and we can start sending numbers to it. Try 3 over send, and then 8 over send - you should see the running total printed in the other Factor instance.

What about channels? We go back to our server, and start a channel there, just as above. This time, though, we publish it to make it available remotely:
USING: channels channels.remote ; <channel> dup publish

What you get in return is an id you can use remotely to communicate. For instance, I just got 326546621698456955263335657082068225943 (yes, they really want to be sure it is unique!).

We will wait on this channel, thereby blocking the UI:
swap from .

In the other Factor instance we use the id to get a reference to the remote channel and write to it
f 9000 <inet4> 326546621698456955263335657082068225943 <remote-channel> "Hello, channels" over to

In the server instance, the message should be printed.

Remote channels and threads are both useful to implement distributed applications and make good use of multicore servers. Of course, it remains the question how to start worker nodes in the first place. Here we have done it manually - if the set of nodes is fixed, this is actually an option.

Otherwise, one could use the io.launcher vocabulary to start other Factor instances programmatically.