chicken-gochan

  1. chicken-gochan
    1. Dependencies
    2. Development Status
    3. Comparison to real Go Channels
    4. Comparison to core.async
    5. API
    6. Samples
    7. TODO

Go-inspired channels for Chicken Scheme. Essentially thread-safe fifo queues that are useful for thread communication and synchronization.

Dependencies

Development Status

Currently supported:

Source code can be found here.

Comparison to real Go Channels

The API and behaviour largely follows Go's channel API, with some exceptions:

Comparison to core.async

Honestly, I wish I had stolen the core.async API instead of the Go channel API since that's already a LISP, but here is what we have for now:

API

[procedure] (gochan capacity)

Construct a channel with a maximum buffer-size of capacity. If capacity is 0, the channel is unbuffered and all its operations will block until a remote end sends/receives.

[procedure] (gochan-select ((chan <-|-> msg [ ok ]) body ...) ... [(else body ...])

This is a channel switch that will send or receive on a single channel, picking whichever clause is able to complete soonest. If no clause is ready, gochan-select will block until one does, unless else is specified which will by execute its body instead of blocking. Multiple send and receive clauses can be specified interchangeably. Note that only one clause will be served.

Here's an example:

    
(gochan-select
  ((chan1 -> msg ok) (if ok (print "chan1 says " msg) (print "chan1 closed!")))
  ((chan2 -> msg ok) (if ok (print "chan2 says " msg) (print "chan2 closed!"))))

Receive clauses, ((chan -> msg [ok]) body ...), execute body with msg bound to the message object and ok bound to a flag indicating success. Receiving from a closed channel immediately completes with the ok flag set to #f.

Send clauses, ((chan <- msg [ok]) body ...), execute body after msg has been sent to a receiver, successfully buffered onto the channel, or if channel was closed. Sending to a closed channel immediately completes with the ok flag set to #f.

A send or receive clause on a closed channel with no ok binding specified will immediately return (void) without executing body. This can be combined with recursion like this:

    
;; loop forever until either chan1 or chan2 closes
(let loop ()
   (gochan-select
    ((chan1 -> msg) (print "chan1 says " msg) (loop))
    ((chan2 <- 123) (print "chan2 got  " 123) (loop))))

Or like this:

    
;; loop forever until chan1 closes. replacing chan2 is important to avoid busy-wait!
(let loop ((chan2 chan2))
  (gochan-select
    ((chan1 -> msg)    (print "chan1 says " msg) (loop chan2))
    ((chan2 -> msg ok) (cond (ok (print "chan2 says " msg) (loop chan2))
                             (else (print "chan2 closed, keep going")
                                   ;; replace chan2 with new forever-blocking channel:
                                   (loop (gochan 0)))))))

gochan-select returns the return-value of the executed clause's body.

To do a non-blocking receive, you can do the following:

    
(gochan-select ((chan1 -> msg ok) (if ok msg #!eof))
               (else 'eagain))

[procedure] (gochan-send chan msg)

This is short for (gochan-select ((chan <- msg))).

[procedure] (gochan-recv chan)

This is short for (gochan-select ((chan -> msg) msg)).

[procedure] (gochan-close chan)

Close the channel. Note that this will unblock existing receivers and senders waiting for an operation on chan.

[procedure] (gochan-after duration/ms)

Return a gochan that will "send" a single message after duration/ms milliseconds of its creation. The message is the (current-milliseconds) value at the time of the timeout (not when the message was received). Receiving more than once on an gochan-after channel will block indefinitely or deadlock the second time.

    
(gochan-select
 ((chan1 -> msg)                (print "chan1 says " msg))
 (((gochan-after 1000) -> when) (print "chan1 took too long")))

You cannot send to or close a timer channel. These are special records that contain information about when the next timer will trigger. Creating timers is a relatively cheap operation, and unlike golang.time.After, may be garbage-collected before the timer triggers. Creating a timer does not spawn a new thread.

[procedure] (gochan-tick duration/ms)

Return a gochan that will "send" a message every duration/ms milliseconds. The message is the (current-milliseconds) value at the time of the tick (not when it was received).

See tests/worker-pool.scm for an example of its use.

[procedure] (go body ...)

Starts and returns a new srfi-18 thread. Short for (thread-start! (lambda () body ...)).

Samples

TODO