default-implementations.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/ (DIR) Log (DIR) Files (DIR) Refs (DIR) Tags (DIR) README (DIR) LICENSE --- default-implementations.lisp (14739B) --- 1 ;;;; -*- indent-tabs-mode: nil -*- 2 3 (in-package #:bordeaux-threads) 4 5 ;;; Helper macros 6 7 (defmacro defdfun (name args doc &body body) 8 `(eval-when (:compile-toplevel :load-toplevel :execute) 9 (unless (fboundp ',name) 10 (defun ,name ,args ,@body)) 11 (setf (documentation ',name 'function) 12 (or (documentation ',name 'function) ,doc)))) 13 14 (defmacro defdmacro (name args doc &body body) 15 `(eval-when (:compile-toplevel :load-toplevel :execute) 16 (unless (fboundp ',name) 17 (defmacro ,name ,args ,@body)) 18 (setf (documentation ',name 'function) 19 (or (documentation ',name 'function) ,doc)))) 20 21 ;;; Thread Creation 22 23 (defdfun start-multiprocessing () 24 "If the host implementation uses user-level threads, start the 25 scheduler and multiprocessing, otherwise do nothing. 26 It is safe to call repeatedly." 27 nil) 28 29 (defdfun make-thread (function &key name 30 (initial-bindings *default-special-bindings*)) 31 "Creates and returns a thread named NAME, which will call the 32 function FUNCTION with no arguments: when FUNCTION returns, the 33 thread terminates. NAME defaults to \"Anonymous thread\" if unsupplied. 34 35 On systems that do not support multi-threading, MAKE-THREAD will 36 signal an error. 37 38 The interaction between threads and dynamic variables is in some 39 cases complex, and depends on whether the variable has only a global 40 binding (as established by e.g. DEFVAR/DEFPARAMETER/top-level SETQ) 41 or has been bound locally (e.g. with LET or LET*) in the calling 42 thread. 43 44 - Global bindings are shared between threads: the initial value of a 45 global variable in the new thread will be the same as in the 46 parent, and an assignment to such a variable in any thread will be 47 visible to all threads in which the global binding is visible. 48 49 - Local bindings, such as the ones introduced by INITIAL-BINDINGS, 50 are local to the thread they are introduced in, except that 51 52 - Local bindings in the the caller of MAKE-THREAD may or may not be 53 shared with the new thread that it creates: this is 54 implementation-defined. Portable code should not depend on 55 particular behaviour in this case, nor should it assign to such 56 variables without first rebinding them in the new thread." 57 (%make-thread (binding-default-specials function initial-bindings) 58 (or name "Anonymous thread"))) 59 60 (defdfun %make-thread (function name) 61 "The actual implementation-dependent function that creates threads." 62 (declare (ignore function name)) 63 (error (make-threading-support-error))) 64 65 (defdfun current-thread () 66 "Returns the thread object for the calling 67 thread. This is the same kind of object as would be returned by 68 MAKE-THREAD." 69 nil) 70 71 (defdfun threadp (object) 72 "Returns true if object is a thread, otherwise NIL." 73 (declare (ignore object)) 74 nil) 75 76 (defdfun thread-name (thread) 77 "Returns the name of the thread, as supplied to MAKE-THREAD." 78 (declare (ignore thread)) 79 "Main thread") 80 81 ;;; Resource contention: locks and recursive locks 82 83 (defdfun lock-p (object) 84 "Returns T if OBJECT is a lock; returns NIL otherwise." 85 (declare (ignore object)) 86 nil) 87 88 (defdfun recursive-lock-p (object) 89 "Returns T if OBJECT is a recursive lock; returns NIL otherwise." 90 (declare (ignore object)) 91 nil) 92 93 (defdfun make-lock (&optional name) 94 "Creates a lock (a mutex) whose name is NAME. If the system does not 95 support multiple threads this will still return some object, but it 96 may not be used for very much." 97 ;; In CLIM-SYS this is a freshly consed list (NIL). I don't know if 98 ;; there's some good reason it should be said structure or that it 99 ;; be freshly consed - EQ comparison of locks? 100 (declare (ignore name)) 101 (list nil)) 102 103 (defdfun acquire-lock (lock &optional wait-p) 104 "Acquire the lock LOCK for the calling thread. 105 WAIT-P governs what happens if the lock is not available: if WAIT-P 106 is true, the calling thread will wait until the lock is available 107 and then acquire it; if WAIT-P is NIL, ACQUIRE-LOCK will return 108 immediately. ACQUIRE-LOCK returns true if the lock was acquired and 109 NIL otherwise. 110 111 This specification does not define what happens if a thread 112 attempts to acquire a lock that it already holds. For applications 113 that require locks to be safe when acquired recursively, see instead 114 MAKE-RECURSIVE-LOCK and friends." 115 (declare (ignore lock wait-p)) 116 t) 117 118 (defdfun release-lock (lock) 119 "Release LOCK. It is an error to call this unless 120 the lock has previously been acquired (and not released) by the same 121 thread. If other threads are waiting for the lock, the 122 ACQUIRE-LOCK call in one of them will now be able to continue. 123 124 This function has no interesting return value." 125 (declare (ignore lock)) 126 (values)) 127 128 (defdmacro with-lock-held ((place) &body body) 129 "Evaluates BODY with the lock named by PLACE, the value of which 130 is a lock created by MAKE-LOCK. Before the forms in BODY are 131 evaluated, the lock is acquired as if by using ACQUIRE-LOCK. After the 132 forms in BODY have been evaluated, or if a non-local control transfer 133 is caused (e.g. by THROW or SIGNAL), the lock is released as if by 134 RELEASE-LOCK. 135 136 Note that if the debugger is entered, it is unspecified whether the 137 lock is released at debugger entry or at debugger exit when execution 138 is restarted." 139 `(when (acquire-lock ,place t) 140 (unwind-protect 141 (locally ,@body) 142 (release-lock ,place)))) 143 144 (defdfun make-recursive-lock (&optional name) 145 "Create and return a recursive lock whose name is NAME. A recursive 146 lock differs from an ordinary lock in that a thread that already 147 holds the recursive lock can acquire it again without blocking. The 148 thread must then release the lock twice before it becomes available 149 for another thread." 150 (declare (ignore name)) 151 (list nil)) 152 153 (defdfun acquire-recursive-lock (lock) 154 "As for ACQUIRE-LOCK, but for recursive locks." 155 (declare (ignore lock)) 156 t) 157 158 (defdfun release-recursive-lock (lock) 159 "Release the recursive LOCK. The lock will only 160 become free after as many Release operations as there have been 161 Acquire operations. See RELEASE-LOCK for other information." 162 (declare (ignore lock)) 163 (values)) 164 165 (defdmacro with-recursive-lock-held ((place &key timeout) &body body) 166 "Evaluates BODY with the recursive lock named by PLACE, which is a 167 reference to a recursive lock created by MAKE-RECURSIVE-LOCK. See 168 WITH-LOCK-HELD etc etc" 169 (declare (ignore timeout)) 170 `(when (acquire-recursive-lock ,place) 171 (unwind-protect 172 (locally ,@body) 173 (release-recursive-lock ,place)))) 174 175 ;;; Resource contention: condition variables 176 177 ;;; A condition variable provides a mechanism for threads to put 178 ;;; themselves to sleep while waiting for the state of something to 179 ;;; change, then to be subsequently woken by another thread which has 180 ;;; changed the state. 181 ;;; 182 ;;; A condition variable must be used in conjunction with a lock to 183 ;;; protect access to the state of the object of interest. The 184 ;;; procedure is as follows: 185 ;;; 186 ;;; Suppose two threads A and B, and some kind of notional event 187 ;;; channel C. A is consuming events in C, and B is producing them. 188 ;;; CV is a condition-variable 189 ;;; 190 ;;; 1) A acquires the lock that safeguards access to C 191 ;;; 2) A threads and removes all events that are available in C 192 ;;; 3) When C is empty, A calls CONDITION-WAIT, which atomically 193 ;;; releases the lock and puts A to sleep on CV 194 ;;; 4) Wait to be notified; CONDITION-WAIT will acquire the lock again 195 ;;; before returning 196 ;;; 5) Loop back to step 2, for as long as threading should continue 197 ;;; 198 ;;; When B generates an event E, it 199 ;;; 1) acquires the lock guarding C 200 ;;; 2) adds E to the channel 201 ;;; 3) calls CONDITION-NOTIFY on CV to wake any sleeping thread 202 ;;; 4) releases the lock 203 ;;; 204 ;;; To avoid the "lost wakeup" problem, the implementation must 205 ;;; guarantee that CONDITION-WAIT in thread A atomically releases the 206 ;;; lock and sleeps. If this is not guaranteed there is the 207 ;;; possibility that thread B can add an event and call 208 ;;; CONDITION-NOTIFY between the lock release and the sleep - in this 209 ;;; case the notify call would not see A, which would be left sleeping 210 ;;; despite there being an event available. 211 212 (defdfun thread-yield () 213 "Allows other threads to run. It may be necessary or desirable to 214 call this periodically in some implementations; others may schedule 215 threads automatically. On systems that do not support 216 multi-threading, this does nothing." 217 (values)) 218 219 (defdfun make-condition-variable (&key name) 220 "Returns a new condition-variable object for use 221 with CONDITION-WAIT and CONDITION-NOTIFY." 222 (declare (ignore name)) 223 nil) 224 225 (defdfun condition-wait (condition-variable lock &key timeout) 226 "Atomically release LOCK and enqueue the calling 227 thread waiting for CONDITION-VARIABLE. The thread will resume when 228 another thread has notified it using CONDITION-NOTIFY; it may also 229 resume if interrupted by some external event or in other 230 implementation-dependent circumstances: the caller must always test 231 on waking that there is threading to be done, instead of assuming 232 that it can go ahead. 233 234 It is an error to call function this unless from the thread that 235 holds LOCK. 236 237 If TIMEOUT is nil or not provided, the system always reacquires LOCK 238 before returning to the caller. In this case T is returned. 239 240 If TIMEOUT is non-nil, the call will return after at most TIMEOUT 241 seconds (approximately), whether or not a notification has occurred. 242 Either NIL or T will be returned. A return of NIL indicates that the 243 lock is no longer held and that the timeout has expired. A return of 244 T indicates that the lock is held, in which case the timeout may or 245 may not have expired. 246 247 **NOTE**: The behavior of CONDITION-WAIT with TIMEOUT diverges from 248 the POSIX function pthread_cond_timedwait. The former may return 249 without the lock being held while the latter always returns with the 250 lock held. 251 252 In an implementation that does not support multiple threads, this 253 function signals an error." 254 (declare (ignore condition-variable lock timeout)) 255 (error (make-threading-support-error))) 256 257 (defdfun condition-notify (condition-variable) 258 "Notify at least one of the threads waiting for 259 CONDITION-VARIABLE. It is implementation-dependent whether one or 260 more than one (and possibly all) threads are woken, but if the 261 implementation is capable of waking only a single thread (not all 262 are) this is probably preferable for efficiency reasons. The order 263 of wakeup is unspecified and does not necessarily relate to the 264 order that the threads went to sleep in. 265 266 CONDITION-NOTIFY has no useful return value. In an implementation 267 that does not support multiple threads, it has no effect." 268 (declare (ignore condition-variable)) 269 (values)) 270 271 ;;; Resource contention: semaphores 272 273 (defdfun make-semaphore (&key name (count 0)) 274 "Create a semaphore with the supplied NAME and initial counter value COUNT." 275 (make-%semaphore :lock (make-lock name) 276 :condition-variable (make-condition-variable :name name) 277 :counter count)) 278 279 (defdfun signal-semaphore (semaphore &key (count 1)) 280 "Increment SEMAPHORE by COUNT. If there are threads waiting on this 281 semaphore, then COUNT of them are woken up." 282 (with-lock-held ((%semaphore-lock semaphore)) 283 (incf (%semaphore-counter semaphore) count) 284 (dotimes (v count) 285 (condition-notify (%semaphore-condition-variable semaphore)))) 286 (values)) 287 288 (defdfun wait-on-semaphore (semaphore &key timeout) 289 "Decrement the count of SEMAPHORE by 1 if the count would not be negative. 290 291 Else blocks until the semaphore can be decremented. Returns generalized boolean 292 T on success. 293 294 If TIMEOUT is given, it is the maximum number of seconds to wait. If the count 295 cannot be decremented in that time, returns NIL without decrementing the count." 296 (with-lock-held ((%semaphore-lock semaphore)) 297 (if (>= (%semaphore-counter semaphore) 1) 298 (decf (%semaphore-counter semaphore)) 299 (let ((deadline (when timeout 300 (+ (get-internal-real-time) 301 (* timeout internal-time-units-per-second))))) 302 ;; we need this loop because of a spurious wakeup possibility 303 (loop until (>= (%semaphore-counter semaphore) 1) 304 do (cond 305 ((null (condition-wait (%semaphore-condition-variable semaphore) 306 (%semaphore-lock semaphore) 307 :timeout timeout)) 308 (return-from wait-on-semaphore)) 309 ;; unfortunately cv-wait may return T on timeout too 310 ((and deadline (>= (get-internal-real-time) deadline)) 311 (return-from wait-on-semaphore)) 312 (timeout 313 (setf timeout (/ (- deadline (get-internal-real-time)) 314 internal-time-units-per-second))))) 315 (decf (%semaphore-counter semaphore)))))) 316 317 (defdfun semaphore-p (object) 318 "Returns T if OBJECT is a semaphore; returns NIL otherwise." 319 (typep object 'semaphore)) 320 321 ;;; Introspection/debugging 322 323 ;;; The following functions may be provided for debugging purposes, 324 ;;; but are not advised to be called from normal user code. 325 326 (defdfun all-threads () 327 "Returns a sequence of all of the threads. This may not 328 be freshly-allocated, so the caller should not modify it." 329 (error (make-threading-support-error))) 330 331 (defdfun interrupt-thread (thread function) 332 "Interrupt THREAD and cause it to evaluate FUNCTION 333 before continuing with the interrupted path of execution. This may 334 not be a good idea if THREAD is holding locks or doing anything 335 important. On systems that do not support multiple threads, this 336 function signals an error." 337 (declare (ignore thread function)) 338 (error (make-threading-support-error))) 339 340 (defdfun destroy-thread (thread) 341 "Terminates the thread THREAD, which is an object 342 as returned by MAKE-THREAD. This should be used with caution: it is 343 implementation-defined whether the thread runs cleanup forms or 344 releases its locks first. 345 346 Destroying the calling thread is an error." 347 (declare (ignore thread)) 348 (error (make-threading-support-error))) 349 350 (defdfun thread-alive-p (thread) 351 "Returns true if THREAD is alive, that is, if 352 DESTROY-THREAD has not been called on it." 353 (declare (ignore thread)) 354 (error (make-threading-support-error))) 355 356 (defdfun join-thread (thread) 357 "Wait until THREAD terminates. If THREAD has already terminated, 358 return immediately. The return values of the thread function are 359 returned." 360 (declare (ignore thread)) 361 (error (make-threading-support-error)))