The Neuron Technical Report
1 A scale-invariant concurrency model
A process is a concurrency primitive based on lightweight threads with extended messaging capabilities. Processes communicate through synchronous, one-way value exchange. Either the sender or receiver can initiate. One side waits to offer an exchange and the other side waits to accept.
Senders can offer to give values to passive takers, but receivers can also offer to take values from passive senders. This generalized model of communication enables push- and pull-based messaging patterns independent of the direction of data flow. When a pair of processes perform complementary operations, the two synchronize and resume evaluation as the exchanged value is delivered.
1.1 A calculus of mediated exchange
The intransitivity of bare channel synchronization complicates the semantics of mediated operations such as forwarding. The following examples illustrate this problem in terms of a forwarding operation.
With channels, the giver blocks to put a value into the forwarder while the taker blocks to get a value from the forwarder. The forwarder accepts a value from the giver as the giver unblocks ahead of the taker. The intended synchronization is now impossible.
In the other direction, the emitter blocks to put a value into the forwarder while the receiver blocks to get a value from the forwarder. The forwarder accepts a value from the emitter as the emitter unblocks ahead of the receiver. Again, the intended synchronization becomes impossible.
Exchangers are an alternative to bare channels that preserve synchronization across mediated exchanges by deferring the synchronizing operation until all sides have committed to the exchange.
1.1.1 Primitive operations
(make-exchanger [ctrl (make-channel)] [data (make-channel)]) ; ex |
An exchanger contains a control channel and a data channel.
(offer ex1 #:to ex2) |
A thread can offer one exchanger to another by putting the first into the control channel of the second.
(accept #:from ex) ; ex* |
A thread can accept an exchanger by getting it from the control channel of another.
(put v #:into ex) |
A thread can put a value into the data channel of an exchanger.
(get #:from ex) ; v |
A thread can get a value from the data channel of an exchanger.
1.1.2 Process exchangers
A process has two exchangers: one for transmitting and another for receiving.
(giver tx rx v) |
(taker rx) |
In a give-take exchange, a giver offers its transmitting exchanger to the receiving exchanger of a taker. After the taker commits to the exchange by accepting the offer, a single value flows through the transmitting exchanger from giver to taker.
(receiver rx tx) |
(emitter tx v) |
In a receive-emit exchange, a receiver offers its receiving exchanger to the transmitting exchanger of an emitter. After the emitter commits to the exchange by accepting the offer, a single value flows through the receiving exchanger from emitter to receiver.
(forwarder ex1 ex2) |
In a forwarding exchange, a mediator accepts an exchanger from one exchanger and then offers it to another.
(filterer ex1 ex2 #:with proc) |
A filtering exchange is a forwarding exchange with a filtering procedure. A mediator accepts an exchanger, wraps its data channel in an impersonator that applies the filtering procedure, then offers the modified exchanger to another.
The filter procedure is applied by the thread that uses the impersonator.
(coupler rx tx [ex (make-exchanger)]) |
In a coupling exchange, a mediator offers an exchanger to two others.
1.1.3 Transitive synchronization
From giver to taker
The giver offers its transmitting exchanger to the forwarder and then blocks to put a value into the exchanger. The forwarder accepts the exchanger from the giver and then offers it to the taker. The taker accepts the giver’s transmitting exchanger from the forwarder and then gets a value as the giver unblocks.
Data and control flow from the giver to the taker. Until the taker is ready to accept, the forwarder blocks to offer and the giver blocks to put, preventing the giver from prematurely synchronizing on the forwarder.
From giver to taker with filter
In a give-take exchange, the filter is applied by the taker on get.
From emitter to receiver
The receiver offers its receiving exchanger to the forwarder and then blocks to get a value from the exchanger. The forwarder accepts the exchanger from the receiver and then offers it to the emitter. The emitter accepts the receiver’s receiving exchanger from the forwarder and then puts a value into it as the receiver unblocks.
Data flows from emitter to receiver, but control flows in the opposite direction. Until the emitter is ready to accept, the forwarder blocks to offer and the receiver blocks to get, preventing the emitter from prematurely synchronizing on the forwarder.
From emitter to receiver with filter
In an emit-receive exchange, the filter is applied by the emitter on put.
From emitter to taker
Couplers are forwarders for emit-take exchanges. The coupler offers an exchanger to a taker and then an emitter. The emitter and taker both accept the exchanger from the coupler and then synchronize by exchanging a value through the shared exchanger.