Concurrency for GoLisp
by Dave Astels
GoLisp now has a few basic concurrency primitives built on top of Go’s goroutines and channels.
I needed the ability to have some code looping in the background for something I’m working on, so I build some primitives using Go’s concurrency capabilities. It’s quite limited, but suffices for my immediate needs. Some time in the future I may add more features. Or you can.
The most basic capability is to be able to run code in the background.
fork primitive accomplishes that.
fork takes a function
of one argument and evaluates it in a new goroutine. The argument to
the function is provided by the
fork primitive. It is an opaque
GoLisp object that wraps a Go struct that stores various bits of
information used by the concurrency primitives. I refer to this as a
process object. This object is also returned by
fork and can be
used to interact with the code running on the goroutine.
Running code in the background is that easy:
(define (run-once proc) (write-line "start") (sleep 1000) (write-line "stop")) (fork run-once) > start [a second goes by] stop [run-once completes and the goroutine terminates]
This code forks
run-once in a fire and forget manner. If we capture
the resulting process object we have the opportunity to interact in a
simple way with the forked code.
The above example used the standard
sleep function. There is a new
sleep function for use in forked code:
proc-sleep (short for process
sleep) that works the same except that it can be canceled. The
proc-sleep function takes a process object in addition to the number
of milliseconds to sleep for. When it returns, the result indicates
whether it was woken prematurely (
#t) or the specified time expired
#f). You can make use of this in the forked code.
Using the process object returned by
fork other code can wake any
proc-sleep in the associated code by using the new
> (define (run proc) (do ((woken #f woken)) (woken (write-line "woken")) (write-line "tick") (set! woken (proc-sleep proc 10000)))) > (define p (fork run)) tick tick [times goes by, tick is printed every 10 seconds] > (wake p) woken [run completes and the goroutine terminates]
proc-sleep wake functions are implemented using a channel so
proc-sleep is called, still does the expected thing:
aborting the sleep immediately.
So, that’s it for forking code and interacting with it. Next up is scheduling code evaluation.
To schedule a function to run at some future time, use the
function. It takes a number of milliseconds and a function. The
function is evaluated on a goroutine after that many milliseconds.
schedule returns immediately with the process object associated with
the scheduled code.
> (define (run-delayed proc) (write-line "running")) > (schedule 10000 run-delayed) [10 seconds pass] running [run-delayed completes and the goroutine terminates]
Once again the scheduled function takes a single parameter which is the
associated process object. That means it can use
proc-sleep and any
other concurrency features we add in the future.
Once scheduled, the goroutine sits there until the time expired and
the function is evaluated. If you use the
abandon function (passing
the process object returned by ` schedule`) you can abort the
goroutine. It will stop waiting for the timeout and the scheduled
function will not be evaluated. It has no effect if used after the
schedule delay has expired.
> (define (run-delayed proc) (write-line "running")) > (define p (schedule 10000 run-delayed)) [5 seconds pass] > (abandon p) [the delay in the is cancelled and the goroutine terminates]
Well, that’s it: basic concurrency support for GoLisp. Enjoy. I look forward to seeing how this is used and extended.
Keep in mind that it’s still new and not rigerously tested, so tread carefully and let me know if you find problems.