Home > Back-end >  Common lisp: calling a class method in a separate thread
Common lisp: calling a class method in a separate thread

Time:02-01

I am trying to build a common lisp implementation of the channel construct of Golang for a personal project (also to learn lisp). So far I've implemented the channels as objects of a class, containing a queue, a lock and a condition variable to signal listening functions that a new message has been added to the queue. I'm using bordeaux threads to create threads, locks, condition variables and join the executions (from the lisp cookbook).

This is the channel class and the recive function:

(defclass channel ()
  ((messages :initform '()
             :accessor messages
             :documentation "Messages in the channel")
   (lock :initform (bt:make-lock)
         :accessor lock
         :documentation
         "Lock to push/pop messages in the channel")
   (cv :initarg :cv
       :initform (bt:make-condition-variable)
       :accessor cv
       :documentation
       "Condtional variable to notify the channel of a new message")))


(defmethod recive-loop ((self channel))
  (with-slots (lock cv messages) self
    (let ((to-ret nil))
    (loop
     (bt:with-lock-held (lock)
       (if (not (null messages))
           (setf to-ret (car (pop messages)))
           (bt:condition-wait cv lock))
       (if to-ret (return to-ret)))))))

(defmethod recive ((self channel))
  (with-slots (name thread) self
    (let ((thread
            (bt:make-thread #'(lambda() (recive-loop self))
                            :name name)))
      (bt:join-thread thread))))

(defmacro gorun (f &rest args)
  (flet ((fn () (apply f args)))
    (bt:make-thread #'fn
            :initial-bindings (list args)
            :name "gorun worker")))

gorun should be the equivalent of go routine() for go (without the light threading). To test the setup I've built a printer function over a channel

(defvar printch (channel))

(defun printover (ch)
  (let ((x (recive ch)))
    (format t "Recived variable x: ~d~%" x)))

but when I run

(gorun printover printch)

The interpreter (using sbcl, but with clisp the same happens) gives back an error:

There is no applicable method for the generic function
  #<STANDARD-GENERIC-FUNCTION COMMON-LISP-USER::RECIVE (1)>
when called with arguments
  (PRINTCH).
   [Condition of type SB-PCL::NO-APPLICABLE-METHOD-ERROR]
See also:
  Common Lisp Hyperspec, 7.6.6 [:section]

Restarts:
 0: [RETRY] Retry calling the generic function.
 1: [ABORT] abort thread (#<THREAD "gorun worker" RUNNING {100293E9F3}>)

Backtrace:
  0: ((:METHOD NO-APPLICABLE-METHOD (T)) #<STANDARD-GENERIC-FUNCTION COMMON-LISP-USER::RECIVE (1)> PRINTCH) [fast-method]
      Locals:
        SB-PCL::ARGS = (PRINTCH)
        GENERIC-FUNCTION = #<STANDARD-GENERIC-FUNCTION COMMON-LISP-USER::RECIVE (1)>
  1: (SB-PCL::CALL-NO-APPLICABLE-METHOD #<STANDARD-GENERIC-FUNCTION COMMON-LISP-USER::RECIVE (1)> (PRINTCH))
      Locals:
        ARGS = (PRINTCH)
        GF = #<STANDARD-GENERIC-FUNCTION COMMON-LISP-USER::RECIVE (1)>
  2: (PRINTOVER PRINTCH)
      Locals:
        CH = PRINTCH
  3: ((LAMBDA NIL :IN BORDEAUX-THREADS::BINDING-DEFAULT-SPECIALS))
      [No Locals]

I'm confused, since the method to run over the channel printch should be the one I've defined.

Trying to call a class method inside of a new thread, but got no applicable method

CodePudding user response:

A macros is supposed to return code to run in place of the original call. Your macro is creating the thread at expansion time.

If you're not using backtick in a macro definition, there's usually something wrong with it. You should figure out what the code would look like without the macro, then define a macro that returns code with that same structure in a backticked list, replacing the places that need to vary with the parameters, using comma to expand them.

(defmacro gorun (f &rest args)
  `(bt:make-thread (function ,f)
        :initial-bindings (list ,@args)
        :name "gorun worker"))

In the above, you need to substitute the function name into a (function ...) expression, and the args list as the :initial-bindings argument.

  • Related