123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- ;; Copyright 2013 Google Inc.
- ;;
- ;; Licensed under the Apache License, Version 2.0 (the "License");
- ;; you may not use this file except in compliance with the License.
- ;; You may obtain a copy of the License at
- ;;
- ;; http://www.apache.org/licenses/LICENSE-2.0
- ;;
- ;; Unless required by applicable law or agreed to in writing, software
- ;; distributed under the License is distributed on an "AS IS" BASIS,
- ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ;; See the License for the specific language governing permissions and
- ;; limitations under the License.
- ;; NOTE: This koan group uses language features specific to sbcl, that are
- ;; not part of the Common Lisp specification. If you are not using sbcl,
- ;; feel free to skip this group by removing it from '.koans'
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Making threads with sb-thread:make-thread ;;
- ;; Joining threads with sb-thread:join-thread ;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; sb-thread takes a -function- as a parameter.
- ;; This function will be executed in a separate thread.
- ;; Since the execution order of separate threads is not guaranteed,
- ;; we must -join- the threads in order to make our assertions.
- (defvar *greeting* "no greeting")
- (defun sets-socal-greeting ()
- (setf *greeting* "Sup, dudes"))
- (define-test test-hello-world-thread
- "Create a thread which returns 'hello world', then ends.
- using a lambda as the supplied function to execute."
- (assert-equal *greeting* "no greeting")
- (let ((greeting-thread
- (sb-thread:make-thread
- (lambda ()
- (setf *greeting* "hello world")))))
- (sb-thread:join-thread greeting-thread)
- (assert-equal *greeting* "hello world")
- (setf greeting-thread (sb-thread:make-thread #'sets-socal-greeting))
- (sb-thread:join-thread greeting-thread)
- (assert-equal *greeting* ____)))
- (define-test test-join-thread-return-value
- "the return value of the thread is passed in sb-thread:join-thread"
- (let ((my-thread (sb-thread:make-thread
- (lambda () (* 11 99)))))
- (assert-equal ____ (sb-thread:join-thread my-thread))))
- (define-test test-threads-can-have-names
- "Threads can have names. Names can be useful in diagnosing problems
- or reporting."
- (let ((empty-plus-thread
- (sb-thread:make-thread #'+
- :name "what is the sum of no things adding?")))
- (assert-equal (sb-thread:thread-name empty-plus-thread)
- ____)))
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Sending arguments to the thread function: ;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (defun returns-hello-name (name)
- (format nil "Hello, ~a" name))
- (defun double-wrap-list (x y z)
- (list (list x y z)))
- ;; Create a thread which will print out "Hello -Name-" using
- ;; the named write-hello-name function. Arguments are handed
- ;; to threads as a list, unless there is just a single argument
- ;; then it does not need to be wrapped in a list.
- (define-test test-sending-arguments-to-thread
- (assert-equal "Hello, Buster"
- (sb-thread:join-thread
- (sb-thread:make-thread 'returns-hello-name
- :arguments "Buster")))
- (assert-equal ____
- (sb-thread:join-thread
- (sb-thread:make-thread 'double-wrap-list
- :arguments '(3 4 5)))))
- ;; ----
- (defvar *accum* 0)
- (defun accum-after-time (time arg1)
- "sleeps for time seconds and then adds arg1 to *accum*"
- (sleep time)
- (incf *accum* arg1))
- (defvar *before-time-millisec* 0)
- (defvar *after-time-millisec* 0)
- ;; cheap and dirty time measuring function
- (defun duration-ms ()
- (- *after-time-millisec* *before-time-millisec*))
- (define-test test-run-in-series
- "get internal real time returns a time stamp in milliseconds"
- (setf *accum* 0)
- (setf *before-time-millisec* (get-internal-real-time))
- (accum-after-time 0.3 1)
- (accum-after-time 0.2 2)
- (accum-after-time 0.1 4)
- (setf *after-time-millisec* (get-internal-real-time))
- (true-or-false? ___ (> (duration-ms) 500))
- (true-or-false? ___ (< (duration-ms) 700))
- (assert-equal *accum* ___))
- (define-test test-run-in-parallel
- "same program as above, executed in threads. Sleeps are simultaneous"
- (setf *accum* 0)
- (setf *before-time-millisec* (get-internal-real-time))
- (let ((thread-1 (sb-thread:make-thread 'accum-after-time :arguments '(0.3 1)))
- (thread-2 (sb-thread:make-thread 'accum-after-time :arguments '(0.2 2)))
- (thread-3 (sb-thread:make-thread 'accum-after-time :arguments '(0.1 4))))
- (sb-thread:join-thread thread-1)
- (sb-thread:join-thread thread-2)
- (sb-thread:join-thread thread-3))
- (setf *after-time-millisec* (get-internal-real-time))
- (true-or-false? ___ (> (duration-ms) 200))
- (true-or-false? ___ (< (duration-ms) 400))
- (assert-equal *accum* ___))
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; killing renegade threads ;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (defun spawn-looping-thread (name)
- "create a never-ending looping thread with a given name"
- (sb-thread:make-thread (lambda () (loop)) :name name))
- (defvar *top-thread* sb-thread:*current-thread*)
- (defun main-thread-p (thread) (eq thread *top-thread*))
- (defun kill-thread-if-not-main (thread)
- " kills a given thread, unless the thread is the main thread.
- returns nil if thread is main.
- returns a 'terminated~' string otherwise"
- (unless (main-thread-p thread)
- (sb-thread:terminate-thread thread)
- (concatenate 'string "terminated " (sb-thread:thread-name thread))))
- (defun kill-spawned-threads ()
- "kill all lisp threads except the main thread."
- (map 'list 'kill-thread-if-not-main (sb-thread:list-all-threads)))
- (defun spawn-three-loopers ()
- "Spawn three run-aways."
- (progn
- (spawn-looping-thread "looper one")
- (spawn-looping-thread "looper two")
- (spawn-looping-thread "looper three")))
- (define-test test-counting-and-killing-threads
- "list-all-threads makes a list of all running threads in this lisp. The sleep
- calls are necessary, as killed threads are not instantly removed from the
- list of all running threads."
- (assert-equal ___ (length (sb-thread:list-all-threads)))
- (kill-thread-if-not-main (spawn-looping-thread "NEVER CATCH ME~! NYA NYA!"))
- (sleep 0.01)
- (assert-equal ___ (length (sb-thread:list-all-threads)))
- (spawn-three-loopers)
- (assert-equal ___ (length (sb-thread:list-all-threads)))
- (kill-spawned-threads)
- (sleep 0.01)
- (assert-equal ___ (length (sb-thread:list-all-threads))))
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; bindings are not inherited across threads ;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (defvar *v* 0)
- (defun returns-v ()
- *v*)
- (define-test test-threads-dont-get-bindings
- "bindings are not inherited across threads"
- (let ((thread-ret-val (sb-thread:join-thread
- (sb-thread:make-thread 'returns-v))))
- (assert-equal thread-ret-val ____))
- (let ((*v* "LEXICAL BOUND VALUE"))
- (assert-equal *v* ____)
- (let ((thread-ret-val (sb-thread:join-thread
- (sb-thread:make-thread 'returns-v))))
- (assert-equal thread-ret-val ____))))
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; global state (special vars) are ;;
- ;; shared across threads ;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (defvar *g* 0)
- (defun waits-and-increments-g (&optional (n 0.2))
- "sets *g* to 1 + the value of *g* n seconds ago"
- (let ((my-remembered-g *g*))
- (sleep n)
- (setq *g* (+ 1 my-remembered-g))))
- (define-test test-serial-wait-and-increment
- "incrementing *g* three times and expecting
- the final value to be three works."
- (setf *g* 0)
- (waits-and-increments-g)
- (waits-and-increments-g)
- (waits-and-increments-g)
- (assert-equal *g* ___))
- (define-test test-parallel-wait-and-increment
- (setf *g* 0)
- (let ((thread-1 (sb-thread:make-thread 'waits-and-increments-g))
- (thread-2 (sb-thread:make-thread 'waits-and-increments-g))
- (thread-3 (sb-thread:make-thread 'waits-and-increments-g)))
- (sb-thread:join-thread thread-1)
- (sb-thread:join-thread thread-2)
- (sb-thread:join-thread thread-3)
- (assert-equal *g* ___)))
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Global state can be protected ;;
- ;; with a mutex. ;;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- (setf *g* 0)
- (defvar *gs-mutex* (sb-thread:make-mutex :name "g's lock"))
- (defun protected-increments-g (&optional (n 0.1))
- "Surround all references to *g* within the with-mutex form."
- (sb-thread:with-mutex (*gs-mutex*)
- (let ((my-remembered-g *g*))
- (sleep n)
- (setq *g* (+ 1 my-remembered-g)))))
- (define-test test-parallel-wait-and-increment-with-mutex
- (setf *g* 0)
- (let ((thread-1 (sb-thread:make-thread 'protected-increments-g))
- (thread-2 (sb-thread:make-thread 'protected-increments-g))
- (thread-3 (sb-thread:make-thread 'protected-increments-g)))
- (sb-thread:join-thread thread-1)
- (sb-thread:join-thread thread-2)
- (sb-thread:join-thread thread-3)
- (assert-equal *g* ___)))
- ;;;;;;;;;;;;;;;;
- ;; Semaphores ;;
- ;;;;;;;;;;;;;;;;
- ;; Incrementing a semaphore is an atomic operation.
- (defvar *g-semaphore* (sb-thread:make-semaphore :name "g" :count 0))
- (defun semaphore-increments-g ()
- (sb-thread:signal-semaphore *g-semaphore*))
- (define-test test-increment-semaphore
- (assert-equal 0 (sb-thread:semaphore-count *g-semaphore*))
- (sb-thread:join-thread (sb-thread:make-thread 'semaphore-increments-g :name "S incrementor 1"))
- (sb-thread:join-thread (sb-thread:make-thread 'semaphore-increments-g :name "S incrementor 2"))
- (sb-thread:join-thread (sb-thread:make-thread 'semaphore-increments-g :name "S incrementor 3"))
- (assert-equal ___ (sb-thread:semaphore-count *g-semaphore*)))
- ;; Semaphores can be used to manage resource allocation, and to trigger
- ;; threads to run when the semaphore value is above zero.
- (defvar *apples* (sb-thread:make-semaphore :name "how many apples" :count 0))
- (defvar *orchard-log* (make-array 10))
- (defvar *next-log-idx* 0)
- (defvar *orchard-log-mutex* (sb-thread:make-mutex :name "orchard log mutex"))
- (defun add-to-log (item)
- (sb-thread:with-mutex (*orchard-log-mutex*)
- (setf (aref *orchard-log* *next-log-idx*) item)
- (incf *next-log-idx*)))
- (defun apple-eater ()
- (sb-thread:wait-on-semaphore *apples*)
- (add-to-log "apple eaten."))
- (defun apple-grower ()
- (sleep 0.1)
- (add-to-log "apple grown.")
- (sb-thread:signal-semaphore *apples*))
- (defun num-apples ()
- (sb-thread:semaphore-count *apples*))
- (define-test test-orchard-simulation
- (assert-equal (num-apples) ___)
- (let ((eater-thread (sb-thread:make-thread 'apple-eater :name "apple eater thread")))
- (let ((grower-thread (sb-thread:make-thread 'apple-grower :name "apple grower thread")))
- (sb-thread:join-thread eater-thread)))
- (assert-equal (aref *orchard-log* 0) ____)
- (assert-equal (aref *orchard-log* 1) ____))
|