This is based on cl-actors, the basic API can be used similarly, but there are some differences and more high level features. It uses a thread-safe queue as a mailbox and actors run on bordeaux-threads.
;; name state args
(defworker/global :counter ((i 0)) (increment)
(sleep 1) ; simulate work
(setf i (+ i increment)) ; increment state
(print i) ; print new value
)
(send :counter 1) ; prints 1 after 1 second
(send :counter 4) ; prints 5 after 2 seconds
;; a hashmap of the workers is available
(gethash :counter *global-workers*)
;;> #<CL-WORKERS/TYPES:WORKER {1002E3E293}>
;; convenience macro to close workers and wait for them to end
(close-and-join-workers :counter)
Joining a worker returns the value its body last returned, so we can get the result of a computation like this:
(defworker/global :fac () (x ag)
(if (= x 0)
(progn (close-worker self) ag) ; store the result
(send self (- x 1) (* x ag))))
(send :fac 4 1)
(join-worker :fac)
24
defworker binds ,name to a function that creates the specified workers
(defworker nagger () ()
(sleep 1)
(print "hi")
(send self) ; message itself, infinite loop
)
;; anonymous actor , no way to stop the nagging
(send (nagger))
Refer to t/cl-workers-test.lisp for more examples
This annoyed me as I generally will just return the current behavior, if you need something like this, you can just keep a lambda in the worker state or mutate the worker’s behav slot.
This works similarly to Golang channels, when a worker recieves a close-signal, it stops working and the thread finishes. This allows other threads to wait for workers.
defworker/global constructs a global singleton worker that can be accessed by its keyword name, this is handy for defining rate limiters and other jobs that should only have one worker
defworker and defworker/global could be one macro handling worker and keyword differently, but I think having them separate might be more clear for users.
I will hopefully add this after experiementing with it independent of this library.
It might be necessary to allow spawning a worker instance to give it some kind of unique ID for the supervisor to recognize it.