Stateful Goroutines in Lisp

(defpackage :stateful-goroutines
  (:use :cl :bordeaux-threads :cl-async))

(in-package :stateful-goroutines)

(defstruct read-op
  key
  (resp (make-channel)))

(defstruct write-op
  key
  val
  (resp (make-channel)))

(defun main ()
  (let ((read-ops 0)
        (write-ops 0)
        (reads (make-channel))
        (writes (make-channel)))

    ;; This function simulates the goroutine that owns the state
    (as:start-event-loop
     (lambda ()
       (let ((state (make-hash-table)))
         (loop
           (as:select
             ((as:chan-recv reads)
              (lambda (read)
                (let ((value (gethash (read-op-key read) state)))
                  (as:chan-send (read-op-resp read) value))))
             ((as:chan-recv writes)
              (lambda (write)
                (setf (gethash (write-op-key write) state) (write-op-val write))
                (as:chan-send (write-op-resp write) t))))))))

    ;; Start 100 threads to perform reads
    (dotimes (r 100)
      (bt:make-thread
       (lambda ()
         (loop
           (let ((read (make-read-op :key (random 5))))
             (as:chan-send reads read)
             (as:chan-recv (read-op-resp read))
             (atomic-incf read-ops)
             (sleep 0.001))))))

    ;; Start 10 threads to perform writes
    (dotimes (w 10)
      (bt:make-thread
       (lambda ()
         (loop
           (let ((write (make-write-op :key (random 5) :val (random 100))))
             (as:chan-send writes write)
             (as:chan-recv (write-op-resp write))
             (atomic-incf write-ops)
             (sleep 0.001))))))

    ;; Let the threads work for a second
    (sleep 1)

    ;; Report the operation counts
    (format t "readOps: ~A~%" read-ops)
    (format t "writeOps: ~A~%" write-ops)))

This example demonstrates how to use channels and threads in Common Lisp to manage shared state, similar to the Go example using goroutines.

In this Lisp version:

  1. We define read-op and write-op structures to encapsulate read and write requests.

  2. The main function sets up channels for reads and writes, and starts a main event loop (using cl-async) to manage the shared state.

  3. We start 100 threads for reads and 10 threads for writes, each continuously sending requests through the respective channels.

  4. The main event loop processes these requests, updating the state accordingly.

  5. After letting the threads run for a second, we print out the total number of read and write operations performed.

To run this program, you would need to ensure you have the necessary libraries installed (bordeaux-threads for threading and cl-async for asynchronous programming and channels).

Running this program would show output similar to:

readOps: 71708
writeOps: 7177

The exact numbers may vary due to the concurrent nature of the program.

This approach demonstrates how to use channels and threads in Common Lisp to manage shared state, providing a similar pattern to Go’s goroutines and channels. While the implementation details differ, the overall concept of using message passing for synchronization remains the same.