How to resolve the algorithm Synchronous concurrency step by step in the Common Lisp programming language

Published on 12 May 2024 09:40 PM

How to resolve the algorithm Synchronous concurrency step by step in the Common Lisp programming language

Table of Contents

Problem Statement

The goal of this task is to create two concurrent activities ("Threads" or "Tasks", not processes.) that share data synchronously. Your language may provide syntax or libraries to perform concurrency. Different languages provide different implementations of concurrency, often with different names. Some languages use the term threads, others use the term tasks, while others use co-processes. This task should not be implemented using fork, spawn, or the Linux/UNIX/Win32 pipe command, as communication should be between threads, not processes. One of the concurrent units will read from a file named "input.txt" and send the contents of that file, one line at a time, to the other concurrent unit, which will print the line it receives to standard output. The printing unit must count the number of lines it prints. After the concurrent unit reading the file sends its last line to the printing unit, the reading unit will request the number of lines printed by the printing unit. The reading unit will then print the number of lines printed by the printing unit. This task requires two-way communication between the concurrent units. All concurrent units must cleanly terminate at the end of the program.

Let's start with the solution:

Step by Step solution about How to resolve the algorithm Synchronous concurrency step by step in the Common Lisp programming language

Source code in the common programming language

(defvar *self*)

(defclass queue ()
  ((condition :initform (make-condition-variable)
              :reader condition-of) 
   (mailbox :initform '()
            :accessor mailbox-of)
   (lock :initform (make-lock)
         :reader lock-of)))

(defun message (recipient name &rest message)
  (with-lock-held ((lock-of recipient))
    ;; it would have been better to implement tail-consing or a LIFO
    (setf (mailbox-of recipient)
          (nconc (mailbox-of recipient)
                 (list (list* name message))))
    (condition-notify (condition-of recipient)))
  message)

(defun mklist (x)
  (if (listp x)
      x
      (list x)))

(defun slurp-message ()
  (with-lock-held ((lock-of *self*))
    (if (not (endp (mailbox-of *self*)))
        (pop (mailbox-of *self*))
        (progn (condition-wait (condition-of *self*)
                               (lock-of *self*))
               (assert (not (endp (mailbox-of *self*))))
               (pop (mailbox-of *self*))))))

(defmacro receive-message (&body cases)
  (let ((msg-name (gensym "MESSAGE")) 
        (block-name (gensym "BLOCK")))
    `(let ((,msg-name (slurp-message))) 
       (block ,block-name
         ,@(loop for i in cases
                 for ((name . case) . body) = (cons (mklist (car i))
                                                    (cdr i))
                 when (typep i '(or (cons (eql quote)
                                          t)
                                    (cons (cons (eql quote) t)
                                          t)))
                   do (warn "~S is a quoted form" i)
                 collect `(when ,(if (null name)
                                     't
                                     `(eql ',name (car ,msg-name)))
                            (destructuring-bind ,case
                                (cdr ,msg-name)
                              (return-from ,block-name
                                (progn ,@body)))))
         (error "Unknown message: ~S" ,msg-name)))))

(defmacro receive-one-message (message &body body)
  `(receive-message (,message . ,body)))

(defun queue () (make-instance 'queue))


(defun reader (pathname writer)
  (with-open-file (stream pathname)
    (loop for line = (read-line stream nil)
          while line
          do (message writer '|here's a line for you| line) 
          finally
       (message writer '|how many lines?|)
       (receive-one-message (|line count| count)
          (format t "line count: ~D~%" count))
       (message writer '|looks like i've got no more lines|))))

(defun writer (stream reader)
  ;; that would work better with ITERATE
  (loop with line-count = 0 do
    (receive-message
     ((|here's a line for you| line)
      (write-line line stream)
      (incf line-count))
     (|looks like i've got no more lines|
      (return))
     (|how many lines?|
      (message reader '|line count| line-count)))))

(defmacro thread (queue &body body)
  `(make-thread (lambda (&aux (*self* ,queue))
                  ,@body)))

(defun synchronous-concurrency (&key (pathname "input.txt"))
  (let ((reader (queue))
        (writer (queue)))
    (thread reader (reader pathname writer))
    (thread writer (writer *standard-output* reader)))
  (values))


CL-USER> (synchronous-concurrency :pathname "/tmp/input.txt")
foo
bar
baz
xenu 666
line count: 4
; No value


  

You may also check:How to resolve the algorithm Classes step by step in the EMal programming language
You may also check:How to resolve the algorithm Test integerness step by step in the Racket programming language
You may also check:How to resolve the algorithm Middle three digits step by step in the PowerShell programming language
You may also check:How to resolve the algorithm Special characters step by step in the PowerShell programming language
You may also check:How to resolve the algorithm Munchausen numbers step by step in the MAD programming language