home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1992 September / Simtel20_Sept92.cdr / msdos / aijournl / aifirst.arc / CLI.LSP < prev    next >
Lisp/Scheme  |  1986-10-30  |  11KB  |  324 lines

  1.  
  2.  
  3. Multitasking Golden Common LISP program
  4.  
  5.  
  6. ;; initialization of parameters
  7. (setf *time-slice* 10)                ; quantum for switching
  8. (setf *beep-switch* t)                ; beep when switching
  9. (setf *random-seed* 10013)
  10. (setf *semaphore-list* nil)
  11. ;; The function which sets up the concurrent processes
  12. (defun cobegin (&rest forms)
  13.   ; initialize 
  14.   (setf *pseudo-time* 0                ; used to count pseudo-time
  15.     *switching?* t                ; inhibit switching if nil
  16.     *concur-length* (list-length forms))
  17.   ; create a list of the correct length for storing results
  18.   (setf stack-results-list (make-list *concur-length*))
  19.   ; create the stack groups
  20.   (make-stack-groups *concur-length*
  21.          (setf *stack-group-names*
  22.                (make-sym-list *concur-length*))
  23.          forms)
  24.   ; initiate task execution
  25.   (switch-around)
  26.   ; return the list of results
  27.   (mapcar 'eval stack-results-list)
  28. )
  29. ;;; The evaluator which handles concurrency
  30. (defun cli_eval (form)
  31.   ; increment the pseudo-time
  32.   (setf *pseudo-time* (1+ *pseudo-time*))
  33.   (cond    
  34.     ; is it time to switch?
  35.     ((and
  36.        ; is switching enabled?
  37.           *switching?*
  38.        ; don't switch if there's only 1 task
  39.        (> *concur-length* 1)
  40.        ; is it the end of a time quantum?
  41.           (>= *pseudo-time* *time-slice*)
  42.        ; don't want to leave the initial (gclisp) stack-group
  43.           (not (equal *current-stack-group*
  44.               *initial-stack-group*)))
  45.      ; if so,
  46.      ; beep if desired
  47.      (if *beep-switch* (beep))
  48.      ; reset pseudo-time
  49.      (setf *pseudo-time* 0)
  50.      ; suspend this task (and return to switch-around)
  51.      (stack-group-return nil)))
  52.   (let* 
  53.      ; evaluate this form
  54.     ((value    (evalhook form #'cli_eval nil))
  55.          ; find the name of this stack-group
  56.      (name (assoc1 '*current-stack-group* *stack-group-names*)))
  57.     ; save the value if appropriate
  58.     (cond (name
  59.            (set (nth (get name 'process-num) stack-results-list) value)))
  60.     ; return the value of form
  61.     value)
  62. )
  63. ;; The scheduler for concurrent execution
  64. (defun switch-around ()
  65.   ; disable switching during the switching
  66.   (setf *switching?* nil)
  67.   (let
  68.        ; choose the next task
  69.        ((next (next-stack *concur-length* *stack-group-names*)))
  70.     (cond
  71.       ; if there are no more tasks, then we're done
  72.       ((null next)
  73.     (setf *switching?* t))
  74.       ; is the task finished?
  75.       ((< 1 (stack-group-status (eval next)))
  76.        ; if so,
  77.        ; eliminate this task
  78.        (setf *stack-group-names*
  79.          (remove next *stack-group-names* ))
  80.        (setf *concur-length* (1- *concur-length*))
  81.        ; make the memory reusable
  82.        (makunbound next)
  83.        ; try another task
  84.        (switch-around))
  85.       ; the task is ready to go
  86.       (t
  87.       (setf *switching?* t)
  88.          ; initiate it
  89.          (funcall (eval next) nil)
  90.          ; when its time-slice is done, we will return to here
  91.          ; and switch again
  92.          (switch-around))))
  93. )
  94. ;; HELP FUNCTIONS
  95. ;; this function returns the status of a stack group
  96. ;;      (0: active, 1:resumable, 2:broken, 3:exhausted)
  97. (defun stack-group-status (stack-group)
  98.   (multiple-value-setq
  99.     (offset segment) (%pointer stack-group))
  100.   (lsh (%contents segment (+ offset 41)) -1)
  101. )
  102. ;;  set up the stack-groups 
  103. (defun make-stack-groups (length name-list1 func-list)
  104.   (cond
  105.     ; done
  106.     ((null name-list1))
  107.     ; otherwise
  108.     (t 
  109.        ; create a stack group of the desired name
  110.        (set (car name-list1)
  111.         (stack-group-preset
  112.                     ; make the stack-group
  113.                     (make-stack-group (car name-list1)
  114.                               ; change as appropriate
  115.                               :regular-pdl-size 6000
  116.                               :special-pdl-size 2000)
  117.                     ; initialize to evaluate the form
  118.                     #'cli_eval (car func-list)))
  119.        ; recursive call to handle the next form
  120.        (make-stack-groups (1- length) (cdr name-list1) (cdr func-list))))
  121. )
  122. ;; create a list of names for stack-groups
  123. (defun make-sym-list (length &optional l)
  124.   (cond
  125.     ; are we done?
  126.     ((= 0 length) l)
  127.     ; nope
  128.     (t
  129.      (let 
  130.           ; create a name
  131.           ((name (gensym)))
  132.        ; give it a process identification number
  133.        (setf (get name 'process-num) (1- length))
  134.        ; recursive call to finish the rest
  135.        (make-sym-list (1- length) (cons name l)))))
  136. )
  137. ;; create a list of unique names with length n
  138. (defun make-list (n &optional l)
  139. (cond
  140.       ((= 0 n) l)
  141.       (t
  142.        (make-list (1- n) (cons (gensym) l))))
  143. )
  144. ;; selects next process to be executed
  145. (defun next-stack (length name-list)
  146.   ; choose the next process randomly
  147.   (nth
  148.        (rand 0 (1- length)) name-list)
  149. )
  150. ;; a random number generator (since Golden doesn't have one built-in)
  151. (defun rand (low-rand high-rand)
  152.   (setf
  153.     *random-seed*
  154.     (truncate (amod (* 25211.0 *random-seed*) 32768.0)))
  155.   (truncate
  156.         (+ low-rand (* (/ (float *random-seed*) 32768.0)
  157.                (1+ (- high-rand low-rand)))))
  158. )
  159. ;; define the mod function (since Golden's is in the editor!)
  160. (defun amod (real-num divisor)
  161.   (- real-num
  162.      (* (truncate (/ real-num divisor))
  163.     divisor))
  164. )
  165. ;; SEMAPHORE FUNCTIONS                                                     
  166. ;; handle the wait function
  167. (defun wait (which)
  168.   ; inhibit task switching
  169.   (setf *switching?* nil)
  170.   (cond 
  171.     ; if the semaphore is set at 1
  172.     ((eq (eval which) 1)
  173.      ; set it to 0 and retun
  174.      (set which 0)
  175.      (setf *switching?* t))
  176.     (t
  177.      ; else put this process on hold
  178.      (let 
  179.           ; find its name
  180.           ((process (assoc1 '*current-stack-group*
  181.                 *stack-group-names*)))
  182.        ; remove it from the ready processes
  183.        (setf *stack-group-names*
  184.          (remove process *stack-group-names*))
  185.        (setf *concur-length*
  186.          (1- *concur-length*))
  187.        ; add it to the queue waiting upon this semaphore
  188.        (setf (get which 'queue)
  189.          (cons process (get which 'queue)))
  190.        ; allow task switching
  191.        (setf *switching?* t)
  192.        ; leave this process (and switch to another)
  193.        (stack-group-return nil))))
  194. )
  195. ;; this function handles the SIGNAL operation.
  196. (defun signal (which)
  197.   ; inhibit task switching
  198.   (setf *switching?* nil)
  199.   (let 
  200.        ; get semaphore's queue
  201.        ((process (get which 'queue)))
  202.     (cond 
  203.       ; are there are tasks waiting upon this semaphore?
  204.       ((not (null process))
  205.        ; if so,
  206.        ; de-queue a task and add it to the ready tasks
  207.        (setf *stack-group-names*
  208.          (cons (car (last process)) *stack-group-names*))
  209.        (setf *concur-length*
  210.          (length *stack-group-names*))
  211.        ; remove the task from this semaphore's queue
  212.        (setf (get which 'queue) (butlast process)))
  213.       ; else set the semaphore to 1
  214.       (t (set which 1))))
  215.     ; enable task switching
  216.   (setf *switching?* t)
  217. )
  218. ;; initializes the semaphores
  219. ;; must be called before initiating concurrent tasking
  220. (defun initialize-semaphores (sl)
  221.   (setf *semaphore-list* (i-s-help sl nil))
  222. )
  223. (defun i-s-help (sl l)
  224.   (cond ((null sl) l)
  225.         (t
  226.          (let ((which (caar sl))
  227.                (value (cadar sl)))
  228.            (set which value)
  229.            (setf (get which 'queue) nil)
  230.            (i-s-help (cdr sl) (cons which l)))))
  231. )
  232. ;; Find the name of a variable in the list given its unique value.
  233. (defun assoc1 (name list)
  234.   (cond ((null list) nil)
  235.     (t (cond ((equal (eval (car list)) (eval name))
  236.           (car list))
  237.          (t (assoc1 name (cdr list))))))
  238. )
  239. ;; EXAMPLES                                   
  240. ; producer-consumer (pc)
  241. ;; The Producer-Consumer Problem (synchronized)
  242. (defun pc ()
  243.   (setf buffer nil)
  244.   (setf information '(this is a test of semaphores))
  245.   ; initializes the semaphores
  246.   (initialize-semaphores '(($ok-to-consume 0) ($ok-to-produce 1)))
  247.   ; starts concurrent reading and writing.
  248.   (cobegin (list 'producer (length information))
  249.        (list 'consumer (length information)))
  250.   )
  251. (defun producer (r)
  252.   (do ((i 0 (1+ i)))
  253.       ((= i r) (print 'end-producer))
  254.     ; start of critical region
  255.     (wait '$ok-to-produce)
  256.     (print 'read-by-producer<---)
  257.     (setf buffer (nth i information))
  258.     (princ buffer)
  259.     (signal '$ok-to-consume)
  260.     ; end of critical region
  261.     )
  262. )
  263. (defun consumer (r)
  264.   (do ((i 0 (1+ i)))
  265.       ((= i r) (print 'end-consumer))
  266.     ; start of critical region
  267.     (wait '$ok-to-consume)
  268.     (print '----print-by-consumer--->)
  269.     (princ buffer)
  270.     (setf buffer nil)
  271.     (signal '$ok-to-produce)
  272.     ; end of critical region
  273.     )
  274. )
  275. ;; The Producer-Consumer Problem (unsynchronized)
  276. (defun un-pc ()
  277.   (setf buffer nil)
  278.   (setf information '(this is a test of semaphores))
  279.   ;; starts concurrent reading and writing.
  280.   (cobegin (list 'un-producer (length information))
  281.        (list 'un-consumer (length information)))
  282. )
  283. (defun un-producer (r)
  284.   (do ((i 0 (1+ i)))
  285.       ((= i r) (print 'end-producer))
  286.     (print 'read-by-producer<---)
  287.     (setf buffer (nth i information))
  288.     (princ buffer)
  289.     (terpri)
  290.     )
  291. )
  292. (defun un-consumer (r)
  293.   (do ((i 0 (1+ i)))
  294.       ((= i r) (print 'end-consumer))
  295.     (print '----print-by-consumer--->)
  296.     (princ buffer)
  297.     (terpri)
  298.     (setf buffer nil)
  299.     )
  300. )
  301. ;; A Note on Error Handling in CLI
  302. ;     The most common error is stack-group-overflow, i.e., running out of
  303. ; memory space.  Try reducing the size of each stack group (see function
  304. ; make-stack-groups). When an error occurs within a concurrent 
  305. ; task, two problems result.
  306. ;     First, the GCLisp error handling routines were not designed to work
  307. ; with stack groups.  In particular, you cannot use Control-G to move up
  308. ; one listener level.  This is because the listeners use the catch-throw
  309. ; construct, and the catch is in the original stack group (the one which
  310. ; initiated concurrent execution) not the one which contains the error.
  311. ; You can use cntrl-C to return to the top-level of the original stack
  312. ; group, but then you are confronted with problem two.
  313. ;     When a stack group is exhausted, its name is unbound (in function
  314. ; switch-around) in order to reclaim the memory used.  However, if there
  315. ; is an error, this unbinding will be skipped.  Worse, GCLisp contains
  316. ; an apparent bug which does not allow reclamation of memory used by a
  317. ; stack group which terminates by being broken (i.e., with an error) 
  318. ; instead of by exhaustion.  Thus, any stack group which terminates in an
  319. ; error will continue to occupy (waste) memory.  The only solution to this
  320. ; problem is to exit GCLisp and restart.
  321. ;;  C. 1986 by Andrew P. Bernat.                                           
  322. ;;  Permission is granted for any noncommercial use with appropriate      
  323. ;;  credit to the author.                                                  
  324.