Newsgroups: comp.lang.scheme
Path: cantaloupe.srv.cs.cmu.edu!bb3.andrew.cmu.edu!newsfeed.pitt.edu!scramble.lm.com!news.math.psu.edu!news.cse.psu.edu!uwm.edu!lll-winken.llnl.gov!nntp.coast.net!zombie.ncsc.mil!news.mathworks.com!uunet!in1.uu.net!news.biu.ac.il!discus.technion.ac.il!news!qobi
From: qobi@eesun.technion.ac.il (Jeffrey Mark Siskind)
Subject: Re: Tasking in Scheme?
Reply-To: Qobi@EE.Technion.AC.IL
Organization: Technion, Israel Institute of Technology
Date: Fri, 10 May 1996 13:16:01 GMT
Message-ID: <QOBI.96May10161601@eesun.technion.ac.il>
In-Reply-To: Thant Tessman's message of 7 May 1996 20:36:40 GMT
X-Nntp-Posting-Host: eesun.technion.ac.il
References: <4mnfec$hor@melmac.gmv.es> <4moc8o$31j@usenet2.interramp.com>
Sender: news@discus.technion.ac.il (News system)
Lines: 284

In article <4moc8o$31j@usenet2.interramp.com> Thant Tessman <thant@interrramp.com> writes:

   > I'm trying to understand how can
   > Multithreading be implemented in Scheme, if it is posible.

   It is possible to build your own single-processor, non-preempted 
   multithreading mechanism on top of call-with-current-continuation.
   And Chez Scheme at least supports an interrupt handler mechanism
   that allows you to write a pre-emptive multithreading system as
   well.

   As for how...

   When I was trying to do this, it seemed that everyone knew it could
   be done, but no one could point me at something that showed me how.
   I eventually managed to figure it out and I've been meaning (in my 
   copious spare time) to put together a description of how to do this 
   (aimed at all my buddies still programming in C/C++).

   In the mean-time, someone mentioned that the sequal to "The 
   Little Schemer" has a good description of call/cc.

   Also, I'm pretty sure that SML/NJ supports multi-processor
   multithreading.

Here is a simple non-preemptive multithreading system that I give my students
to play with. It runs under Scheme->C and will pop up virtual dumb terminals
under X, each of which is running a READ-EVAL-PRINT loop. I've tried to make
this preemptive by using Unix `alarm' but it crashes. I don't know why.

This is a relatively fancy implementation that provides per-process variables.
Currently the current input and output ports are per-processs but you can add
more by modifying SAVE-PROCESS-VARIABLES. I actually develop this version
incrementally in class in about a half dozen steps from a much simpler version.

(kill! process) - Kills a process.

(create-process thunk) - Creates a process that will run thunk.

(relinquish x) - The identity function except that it causes a task switch.

(start-scheduler) - Start the scheduler. It terminates when the last process
terminates.

(make-terminal-port) - Make a dumb terminal in an X window and return a Scheme
port that reads and writes to that terminal.

(shell port) - Run a READ-EVAL-PRINT loop on port.

(ipl n) - `Boot the system.' Create n terminals, start n processes running
shells in those terminals, and start the scheduler.

	Jeff
-------------------------------------------------------------------------------
(define-structure process thunk live?)

(define *processes* '())

(define *when-shutting-down* #f)

(define *ttys* #f)

(define *tty-processes* #f)

(define (commit-suicide) (panic "Scheduler not running"))

(define (save-process-variables)
 (let ((saved-current-input-port (current-input-port))
       (saved-current-output-port (current-output-port))
       (saved-commit-suicide commit-suicide))
  (lambda ()
   (set-current-input-port! saved-current-input-port)
   (set-current-output-port! saved-current-output-port)
   (set! commit-suicide saved-commit-suicide))))

(define (kill! process)
 (unless (memq process *processes*) (panic "Not a process"))
 (cond ((eq? (process-live? process) 'fetus)
	(set! *processes* (removeq process *processes*)))
       ((eq? process (first *processes*)) (commit-suicide))
       ((process-live? process)	(set-process-live?! process #f))
       (else (panic "Already dead")))
 #f)

(define (create-process thunk)
 (let ((process (make-process #f 'fetus)))
  (set-process-thunk!
   process
   (lambda ()
    (call-with-current-continuation
     (lambda (continuation)
      (set! commit-suicide (lambda () (continuation #f)))
      (set-process-live?! process #t)
      (thunk)))
    (set! *processes* (rest *processes*))
    (if (null? *processes*)
	(*when-shutting-down*)
	((process-thunk (first *processes*))))))
  (set! *processes* (append *processes* (list process)))
  process))

(define (relinquish x)
 (if *when-shutting-down*
     (call-with-current-continuation
      (lambda (continuation)
       (let ((restore-process-variables (save-process-variables)))
	(set-process-thunk!
	 (first *processes*)
	 (lambda ()
	  (restore-process-variables)
	  (if (process-live? (first *processes*))
	      (continuation x)
	      (commit-suicide))))
	(set! *processes*
	      (append (rest *processes*) (list (first *processes*))))
	((process-thunk (first *processes*))))))
     x))

(define (start-scheduler)
 (when *when-shutting-down* (panic "Scheduler already running"))
 (when (null? *processes*) (panic "No processes to run"))
 (call-with-current-continuation
  (lambda (continuation)
   (let ((restore-process-variables (save-process-variables)))
    (set! *when-shutting-down*
	  (lambda ()
	   (restore-process-variables)
	   (set! *when-shutting-down* #f)
	   (continuation #f))))
   ((process-thunk (first *processes*))))))

(define (make-terminal-port)
 (let* ((display (xopendisplay ""))
	(screen (xdefaultscreen display))
	(white-pixel (xwhitepixel display screen))
	(black-pixel (xblackpixel display screen))
	(font (xloadqueryfont display "9x15"))
	(font-height
	 (quotient (second (xgetfontproperty font xa_point_size)) 10))
	(font-width (xtextwidth font "A" 1))
	(window (xcreatesimplewindow
		 display (xrootwindow display screen) 0 0
		 (* 80 font-width) (* 24 font-height) 1
		 black-pixel white-pixel))
	(gc (xcreategc display window 0 (make-xgcvalues)))
	(event (make-xevent))
	(memory (make-vector 24))
	(row 0)
	(column 0)
	(width 80)
	(circle #t)
	(level #f)
	(wlength #f)
	(pretty #f)
	(echo-port #f)
	(next-character #f))
  (define (close-port)
   (xfreegc display gc)
   (xunloadfont display (xfontstruct-fid font))
   (xdestroywindow display window)
   (xclosedisplay display))
  (define (put-new-line)
   (set! column 0)
   (cond ((= row 23)
	  (do ((i 0 (+ i 1))) ((= i 23))
	   (vector-set! memory i (vector-ref memory (+ i 1))))
	  (vector-set! memory 23 (make-string 80 #\space)))
	 (else (set! row (+ row 1))
	       (vector-set! memory row (make-string 80 #\space))))
   (write-flush))
  (define (write-char character)
   (unless (char? character)
    (panic "Argument to WRITE-CHAR is not a character"))
   (cond ((char=? character #\newline) (put-new-line))
	 (else (string-set! (vector-ref memory row) column character)
	       (set! column (+ column 1))
	       (when (= column 80) (put-new-line)))))
  (define (get-character)
   (let loop ()
    (if (ycheckwindowevent display window keypressmask event)
	(if (and (eq? (xevent-xany-type event) keypress)
		 (= (string-length (ylookupstring event)) 1))
	    (let ((character (string-ref (ylookupstring event) 0)))
	     (when (char=? character #\return) (set! character #\newline))
	     (write-char character)
	     (write-flush)
	     character)
	    (loop))
	(begin (relinquish #f) (loop)))))
  (define (read-char)
   (cond (next-character
	  (let ((character next-character))
	   (set! next-character #f)
	   character))
	 (else (get-character))))
  (define (peek-char)
   (if next-character next-character (set! next-character (get-character))))
  (define (write-token token)
   (cond ((char? token) (write-char token))
	 ((or (pair? token) (null? token)) (for-each write-char token))
	 (else (let ((len (string-length token)))
		(do ((i 0 (+ i 1))) ((= i len))
		 (write-char (string-ref token i)))))))
  ;; needs work: The following are not fully implemented.
  (define (char-ready?) #f)
  (define (write-count) column)
  (define (write-width) width)
  (define (write-width! w) (set! width w))
  (define (write-flush)
   (xclearwindow display window)
   (do ((i 0 (+ i 1))) ((= i 24))
    (xdrawstring
     display window gc 0 (* (+ i 1) font-height) (vector-ref memory i) 80))
   (xflush display)
   (relinquish #f))
  (define (write-circle) circle)
  (define (write-circle! c) (set! circle c))
  (define (write-level) level)
  (define (write-level! l) (set! level l))
  (define (write-length) wlength)
  (define (write-length! l) (set! wlength l))
  (define (write-pretty) pretty)
  (define (write-pretty! p) (set! pretty p))
  (define (echo) echo-port)
  (define (echo! p) (set! echo-port p))
  (do ((i 0 (+ i 1))) ((= i 24))
   (vector-set! memory i (make-string 80 #\space)))
  (xstorename display window "Scheme Terminal")
  (xseticonname display window "Scheme Terminal")
  (xselectinput display window (bit-or exposuremask keypressmask))
  (xsetbackground display gc white-pixel)
  (xsetforeground display gc black-pixel)
  (xsetfont display gc (xfontstruct-fid font))
  (xmapsubwindows display window)
  (xmapraised display window)
  (xflush display)
  (ywindowevent display window exposuremask event)
  (cons 'port
	(lambda (method)
	 (case method
	  ;; don't implement FILE-PORT WRITE-ECHO WRITE-TOKEN-ECHO
	  ;;                 READ-CHAR-ECHO GET-OUTPUT-STRING
	  ((close-port) close-port)
	  ((read-char) read-char)
	  ((peek-char) peek-char)
	  ((write-char) write-char)
	  ((write-token) write-token)
	  ((write-width) write-width)
	  ((write-width!) write-width!)
	  ((char-ready?) char-ready?)
	  ((write-count) write-count)
	  ((write-flush) write-flush)
	  ((write-circle) write-circle)
	  ((write-circle!) write-circle!)
	  ((write-level) write-level)
	  ((write-level!) write-level!)
	  ((write-length) write-length)
	  ((write-length!) write-length!)
	  ((write-pretty) write-pretty)
	  ((write-pretty!) write-pretty!)
	  (else #f))))))

(define (shell port)
 (set-current-input-port! port)
 (set-current-output-port! port)
 (display "Welcome to the Scheme Timesharing System")
 (newline)
 (let loop ()
  (display "> ")
  (let ((expression (reentrant-read)))
   (newline)
   (write (eval expression)))
  (newline)
  (loop)))

(define (ipl n)
 (set! *ttys* (map-n (lambda (i) (make-terminal-port)) n))
 (set! *tty-processes*
       (map (lambda (tty) (create-process (lambda () (shell tty)))) *ttys*))
 (start-scheduler)
 (for-each close-port *ttys*))
-- 

    Jeff (home page http://tochna.technion.ac.il/~qobi)
