source: project/wiki/man/4/Unit srfi-18 @ 33130

Last change on this file since 33130 was 33130, checked in by sjamaan, 5 years ago

Sync wiki manual with core 4.10.0 changes (which was apparently forgotten when we made the release...)

File size: 39.7 KB
Line 
1[[tags: manual]]
2
3[[toc:]]
4
5== Unit srfi-18
6
7A multithreading package, largely following the specification
8of [[http://srfi.schemers.org/srfi-18/srfi-18.html|SRFI-18]].  This
9document contains the core of the SRFI-18 documentation as well as
10information on CHICKEN deviations from the spec.
11
12The threads implemented in CHICKEN are so called "green" threads,
13based on first-class continuations. Native threads that map directly
14to the threads provided by the operating system are not supported.
15The advantage of this is that threads are very lightweight and
16somewhat larger degree of determinism. The disadvantage is that
17execution of Scheme code on multiple processor cores is not available.
18
19SRFI-18 defines the following multithreading datatypes:
20
21* Thread
22* Mutex
23* Condition variable
24* Time
25
26It also defines a mechanism to handle exceptions and some multithreading
27exception datatypes.
28
29== CHICKEN implementation
30
31=== Notes
32
33* {{thread-start!}} accepts a thunk (a zero argument procedure) as argument, which is equivalent to {{(thread-start! (make-thread THUNK))}}.
34
35* {{thread-sleep!}} accepts a seconds real number value in addition to a time object.
36
37* When an uncaught exception (i.e. an error) is signalled in a thread other than the primordial thread and warnings are enabled (see: {{enable-warnings}}, then a warning message is written to the port that is the value of {{(current-error-port)}}.
38
39* Blocking I/O will block all threads, except for some socket operations (see the section about the {{tcp}} unit). An exception is the read-eval-print loop on UNIX platforms: waiting for input will not block other threads, provided the current input port reads input from a console.
40
41* It is generally not a good idea for one thread to call a continuation created by another thread, if {{dynamic-wind}} is involved.
42
43* When more than one thread compete for the current time-slice, the thread that was waiting first will become the next runnable thread.
44
45* The dynamic environment of a thread consists of the following state:
46
47** The current input-, output- and error-port
48
49** The current exception handler
50
51** The values of all current parameters (created by {{make-parameter}})
52
53** Any pending {{dynamic-wind}} thunks.
54
55* When an error is triggered inside the execution context of a thread, the default exception-handler will simply terminate the thread (and store the error condition for later use). Pending {{dynamic-wind}} thunks will ''not'' be invoked. Use a custom exception handler for the thread in that case.
56
57=== Procedures
58
59The following procedures are provided in addition to the procedures defined in SRFI-18.
60
61<procedure>(thread-signal! THREAD X)</procedure>
62
63This will cause {{THREAD}} to signal the condition {{X}} once it is scheduled
64for execution. After signalling the condition, the thread continues with its normal
65execution.
66
67<procedure>(thread-quantum THREAD)</procedure>
68
69Returns the quantum of {{THREAD}}, which is an exact integer
70specifying the approximate time-slice of the thread in milliseconds.
71
72<procedure>(thread-quantum-set! THREAD QUANTUM)</procedure>
73
74Sets the quantum of {{THREAD}} to {{QUANTUM}}.
75
76<procedure>(thread-suspend! THREAD)</procedure>
77
78Suspends the execution of {{THREAD}} until resumed.
79
80<procedure>(thread-resume! THREAD)</procedure>
81
82Readies the suspended thread {{THREAD}}.
83
84<procedure>(thread-wait-for-i/o! FD [MODE])</procedure>
85
86Suspends the current thread until input ({{MODE}} is {{#:input}}), output ({{MODE}} is {{#:output}})
87or both ({{MODE}} is {{#:all}}) is available. {{FD}} should be a file-descriptor (not a port!) open
88for input or output, respectively.
89
90<procedure>(thread-state thread)</procedure><br>
91
92Returns information about the state of the {{thread}}. The possible results
93are:
94
95
96* '''symbol {{created}}''': the {{thread}} is in the created state
97* '''symbol {{ready}}''': the {{thread}} is in the ready state
98* '''symbol {{running}}''': the {{thread}} is in the running state
99* '''symbol {{blocked}}''': the {{thread}} is in the blocked state
100* '''symbol {{suspended}}''': the {{thread}} is in the suspended state
101* '''symbol {{sleeping}}''': the {{thread}} is in the sleeping state
102* '''symbol {{terminated}}''': the {{thread}} is in the terminated state
103* '''symbol {{dead}}''': the {{thread}} is in the dead state
104
105
106== SRFI-18 specification
107
108The thread system provides the following data types:
109
110* Thread (a virtual processor which shares object space with all other threads)
111* Mutex (a mutual exclusion device, also known as a lock and binary semaphore)
112* Condition variable (a set of blocked threads)
113* Time (an absolute point on the time line)
114
115Some multithreading exception datatypes are also specified, and a general
116mechanism for handling exceptions.
117
118=== Background information
119
120==== Threads
121
122A "running" thread is a thread that is currently executing. There can be
123more than one running thread on a multiprocessor machine. A "runnable"
124thread is a thread that is ready to execute or running. A thread is
125"blocked" if it is waiting for a mutex to become unlocked, an I/O operation
126to become possible, the end of a "sleep" period, etc. A "new" thread is a
127thread that has not yet become runnable. A new thread becomes runnable when
128it is started. A "terminated" thread is a thread that can no longer become
129runnable (but "deadlocked" threads are not considered terminated). The
130only valid transitions between the thread states are from new to runnable,
131between runnable and blocked, and from any state to terminated:
132
133
134                          unblock
135        start            <-------
136   NEW -------> RUNNABLE -------> BLOCKED
137     \             |      block  /
138      \            v            /
139       +-----> TERMINATED <----+
140
141
142Each thread has a "specific" field which can be used in an application
143specific way to associate data with the thread (some thread systems call
144this "thread local storage").
145
146==== Mutexes
147
148A mutex can be in one of four states: locked (either owned or not owned)
149and unlocked (either abandoned or not abandoned). An attempt to lock
150a mutex only succeeds if the mutex is in an unlocked state, otherwise
151the current thread must wait. A mutex in the locked/owned state has an
152associated "owner" thread, which by convention is the thread that is
153responsible for unlocking the mutex (this case is typical of critical
154sections implemented as "lock mutex, perform operation, unlock mutex"). A
155mutex in the locked/not-owned state is not linked to a particular thread.
156A mutex becomes locked when a thread locks it using the {{mutex-lock!}}
157primitive. A mutex becomes unlocked/abandoned when the owner of a
158locked/owned mutex terminates. A mutex becomes unlocked/not-abandoned
159when a thread unlocks it using the {{mutex-unlock!}} primitive. The mutex
160primitives specified in this SRFI do not implement "recursive" mutex
161semantics; an attempt to lock a mutex that is locked implies that the
162current thread must wait even if the mutex is owned by the current thread
163(this can lead to a deadlock if no other thread unlocks the mutex).
164
165Each mutex has a "specific" field which can be used in an application
166specific way to associate data with the mutex.
167
168
169==== Condition variables
170
171A condition variable represents a set of blocked threads. These blocked
172threads are waiting for a certain condition to become true. When a thread
173modifies some program state that might make the condition true, the thread
174unblocks some number of threads (one or all depending on the primitive
175used) so they can check the value of the condition. This allows complex
176forms of interthread synchronization to be expressed more conveniently than
177with mutexes alone.
178
179Each condition variable has a "specific" field which can be used in an
180application specific way to associate data with the condition variable.
181
182
183==== Fairness
184
185In various situations the scheduler must select one thread from a set of
186threads (e.g. which thread to run when a running thread blocks or expires
187its quantum, which thread to unblock when a mutex unlocks or a condition
188variable is signaled). The constraints on the selection process determine
189the scheduler's "fairness". Typically the selection depends on the order in
190which threads become runnable or blocked and on some "priority" attached to
191the threads.
192
193Because we do not wish to preclude extensions to this SRFI (such as for
194real-time multithreading) that require specific fairness constraints, there
195are no fairness constraints imposed by this SRFI. It is expected however
196that implementations of Scheme that support this SRFI will document the
197fairness constraints they provide.
198
199
200==== Memory coherency and lack of atomicity
201
202Read and write operations on the store (such as reading and writing a
203variable, an element of a vector or a string) are not required to be
204atomic. It is an error for a thread to write a location in the store
205while some other thread reads or writes that same location. It is the
206responsibility of the application to avoid write/read and write/write races
207through appropriate uses of the synchronization primitives.
208
209Concurrent reads and writes to ports are allowed. It is the responsibility
210of the implementation to serialize accesses to a given port using the
211appropriate synchronization primitives.
212
213
214==== Dynamic environments, continuations and {{dynamic-wind}}
215
216The "dynamic environment" is a structure which allows the system to find
217the value returned by {{current-input-port}}, {{current-output-port}},
218etc. The procedures {{with-input-from-file}}, {{with-output-to-file}},
219etc extend the dynamic environment to produce a new dynamic environment
220which is in effect for the duration of the call to the thunk passed as the
221last argument. Some Scheme systems generalize the dynamic environment by
222providing procedures and special forms to define new "dynamic variables"
223and bind them in the dynamic environment (e.g. {{make-parameter}} and
224{{parameterize}}).
225
226Each thread has its own dynamic environment. When a thread's dynamic
227environment is extended this does not affect the dynamic environment
228of other threads. When a thread creates a continuation, the thread's
229dynamic environment and the {{dynamic-wind}} stack are saved within
230the continuation (an alternate but equivalent point of view is that the
231{{dynamic-wind}} stack is part of the dynamic environment). When this
232continuation is invoked the required {{dynamic-wind}} before and after
233thunks are called and the saved dynamic environment is reinstated as the
234dynamic environment of the current thread. During the call to each required
235{{dynamic-wind}} before and after thunk, the dynamic environment and the
236{{dynamic-wind}} stack in effect when the corresponding {{dynamic-wind}}
237was executed are reinstated. Note that this specification clearly defines
238the semantics of calling {{call-with-current-continuation}} or invoking a
239continuation within a before or after thunk. The semantics are well defined
240even when a continuation created by another thread is invoked. Below is an
241example exercising the subtleties of this semantics.
242
243
244     (with-output-to-file
245      "foo"
246      (lambda ()
247        (let ((k (call-with-current-continuation
248                  (lambda (exit)
249                    (with-output-to-file
250                     "bar"
251                     (lambda ()
252                       (dynamic-wind
253                        (lambda () (write '(b1)))
254                        (lambda ()
255                          (let ((x (call-with-current-continuation
256                                    (lambda (cont) (exit cont)))))
257                            (write '(t1))
258                            x))
259                        (lambda () (write '(a1))))))))))
260          (if k
261              (dynamic-wind
262               (lambda () (write '(b2)))
263               (lambda ()
264                 (with-output-to-file
265                  "baz"
266                  (lambda ()
267                    (write '(t2))
268                    ; go back inside (with-output-to-file "bar" ...)
269                    (k #f))))
270               (lambda () (write '(a2))))))))
271
272In an implementation of Scheme where {{with-output-to-file}} only closes
273the port it opened when the thunk returns normally, then the following
274actions will occur: {{(b1)(a1)}} is written to "bar", {{(b2)}} is written
275to "foo", {{(t2)}} is written to "baz", {{(a2)}} is written to "foo", and
276{{(b1)(t1)(a1)}} is written to "bar".
277
278When the scheduler stops the execution of a running thread T1 (whether
279because it blocked, expired its quantum, was terminated, etc) and then
280resumes the execution of a thread T2, there is in a sense a transfer of
281control between T1's current continuation and the continuation of T2. This
282transfer of control by the scheduler does not cause any {{dynamic-wind}}
283before and after thunks to be called. It is only when a thread itself
284transfers control to a continuation that {{dynamic-wind}} before and after
285thunks are called.
286
287
288==== Time objects and timeouts
289
290A time object represents a point on the time line. Its resolution is
291implementation dependent (implementations are encouraged to implement at
292least millisecond resolution so that precise timing is possible). Using
293{{time->seconds}} and {{seconds->time}}, a time object can be converted
294to and from a real number which corresponds to the number of seconds from
295a reference point on the time line. The reference point is implementation
296dependent and does not change for a given execution of the program (e.g.
297the reference point could be the time at which the program started).
298
299All synchronization primitives which take a timeout parameter accept three
300types of values as a timeout, with the following meaning:
301
302
303* a time object represents an absolute point in time
304* an exact or inexact real number represents a relative time in seconds from the moment the primitive was called
305* {{#f}} means that there is no timeout
306
307When a timeout denotes the current time or a time in the past, the
308synchronization primitive claims that the timeout has been reached only
309after the other synchronization conditions have been checked. Moreover the
310thread remains running (it does not enter the blocked state). For example,
311{{(mutex-lock! m 0)}} will lock mutex {{m}} and return {{#t}} if {{m}} is
312currently unlocked, otherwise {{#f}} is returned because the timeout is
313reached.
314
315
316==== Primitives and exceptions
317
318When one of the primitives defined in this SRFI raises an exception defined
319in this SRFI, the exception handler is called with the same continuation
320as the primitive (i.e. it is a tail call to the exception handler). This
321requirement avoids having to use {{call-with-current-continuation}} to get
322the same effect in some situations.
323
324
325==== Primordial thread
326
327The execution of a program is initially under the control of a single
328thread known as the "primordial thread". The primordial thread has an
329unspecified name, specific field, dynamic environment, {{dynamic-wind}}
330stack, and exception handler. All threads are terminated when the
331primordial thread terminates (normally or not).
332
333
334=== Procedures
335
336<procedure>(current-thread)</procedure><br>
337
338Returns the current thread.
339
340
341     (eq? (current-thread) (current-thread))  ==>  #t
342
343
344<procedure>(thread? obj)</procedure><br>
345
346Returns {{#t}} if {{obj}} is a thread, otherwise returns {{#f}}.
347
348
349     (thread? (current-thread))  ==>  #t
350     (thread? 'foo)              ==>  #f
351
352<procedure>(make-thread thunk [name])</procedure><br>
353
354Returns a new thread. This thread is not automatically made runnable
355(the procedure {{thread-start!}} must be used for this).
356
357A thread has the following fields: name, specific, end-result,
358end-exception, and a list of locked/owned mutexes it owns. The
359thread's execution consists of a call to ''thunk'' with the "initial
360continuation". This continuation causes the (then) current thread to
361store the result in its end-result field, abandon all mutexes it owns,
362and finally terminate. The {{dynamic-wind}} stack of the initial
363continuation is empty. The optional {{name}} is an arbitrary Scheme
364object which identifies the thread (useful for debugging); it defaults
365to an unspecified value. The specific field is set to an unspecified
366value.
367
368The thread inherits the dynamic environment from the current
369thread. Moreover, in this dynamic environment the exception handler is
370bound to the "initial exception handler" which is a unary procedure
371which causes the (then) current thread to store in its end-exception
372field an "uncaught exception" object whose "reason" is the argument of
373the handler, abandon all mutexes it owns, and finally terminate.
374
375     (make-thread (lambda () (write 'hello)))  ==>  ''a thread''
376
377<procedure>(thread-name thread)</procedure><br>
378
379Returns the name of the {{thread}}.
380
381
382     (thread-name (make-thread (lambda () #f) 'foo))  ==>  foo
383
384<procedure>(thread-specific thread)</procedure><br>
385
386Returns the content of the {{thread}}'s specific field.
387
388<procedure>(thread-specific-set! thread obj)</procedure><br>
389
390Stores {{obj}} into the {{thread}}'s specific field.
391{{thread-specific-set!}} returns an unspecified value.
392
393     (thread-specific-set! (current-thread) "hello")  ==>  ''unspecified''
394 
395     (thread-specific (current-thread))               ==>  "hello"
396
397Alternatively, you can use
398
399     (set! (thread-specific (current-thread)) "hello")
400
401<procedure>(thread-start! thread)</procedure><br>
402
403Makes {{thread}} runnable. The {{thread}} must be a new thread.
404{{thread-start!}} returns the {{thread}}.
405
406
407     (let ((t (thread-start! (make-thread (lambda () (write 'a))))))
408       (write 'b)
409       (thread-join! t))             ==>  ''unspecified''
410                                          ''after writing'' ab ''or'' ba
411
412NOTE: It is useful to separate thread creation and thread activation to
413avoid the race condition that would occur if the created thread tries to
414examine a table in which the current thread stores the created thread. See
415the last example of {{thread-terminate!}} which contains mutually recursive
416threads.
417
418<procedure>(thread-yield!)</procedure><br>
419
420The current thread exits the running state as if its quantum had expired.
421{{thread-yield!}} returns an unspecified value.
422
423
424     ; a busy loop that avoids being too wasteful of the CPU
425 
426     (let loop ()
427       (if (mutex-lock! m 0) ; try to lock m but don't block
428           (begin
429             (display "locked mutex m")
430             (mutex-unlock! m))
431           (begin
432             (do-something-else)
433             (thread-yield!) ; relinquish rest of quantum
434             (loop))))
435
436<procedure>(thread-sleep! timeout)</procedure><br>
437
438The current thread waits until the timeout is reached. This blocks the
439thread only if {{timeout}} represents a point in the future. It is an error
440for {{timeout}} to be {{#f}}. {{thread-sleep!}} returns an unspecified
441value.
442
443
444     ; a clock with a gradual drift:
445 
446     (let loop ((x 1))
447       (thread-sleep! 1)
448       (write x)
449       (loop (+ x 1)))
450 
451     ; a clock with no drift:
452 
453     (let ((start (time->seconds (current-time)))
454       (let loop ((x 1))
455         (thread-sleep! (seconds->time (+ x start)))
456         (write x)
457         (loop (+ x 1))))
458
459<procedure>(thread-terminate! thread)</procedure><br>
460
461Causes an abnormal termination of the {{thread}}. If the {{thread}}
462is not already terminated, all mutexes owned by the {{thread}} become
463unlocked/abandoned and a "terminated thread exception" object is stored in
464the {{thread}}'s end-exception field. If {{thread}} is the current thread,
465{{thread-terminate!}} does not return. Otherwise {{thread-terminate!}}
466returns an unspecified value; the termination of the {{thread}} will occur
467before {{thread-terminate!}} returns.
468
469
470     (thread-terminate! (current-thread))  ==>  ''does not return''
471 
472     (define (amb thunk1 thunk2)
473       (let ((result #f)
474             (result-mutex (make-mutex))
475             (done-mutex (make-mutex)))
476         (letrec ((child1
477                   (make-thread
478                     (lambda ()
479                       (let ((x (thunk1)))
480                         (mutex-lock! result-mutex #f #f)
481                         (set! result x)
482                         (thread-terminate! child2)
483                         (mutex-unlock! done-mutex)))))
484                  (child2
485                   (make-thread
486                     (lambda ()
487                       (let ((x (thunk2)))
488                         (mutex-lock! result-mutex #f #f)
489                         (set! result x)
490                         (thread-terminate! child1)
491                         (mutex-unlock! done-mutex))))))
492           (mutex-lock! done-mutex #f #f)
493           (thread-start! child1)
494           (thread-start! child2)
495           (mutex-lock! done-mutex #f #f)
496           result)))
497
498
499NOTE: This operation must be used carefully because it terminates a
500thread abruptly and it is impossible for that thread to perform any kind
501of cleanup. This may be a problem if the thread is in the middle of a
502critical section where some structure has been put in an inconsistent
503state. However, another thread attempting to enter this critical
504section will raise an "abandoned mutex exception" because the mutex is
505unlocked/abandoned. This helps avoid observing an inconsistent state. Clean
506termination can be obtained by polling, as shown in the example below.
507
508
509     (define (spawn thunk)
510       (let ((t (make-thread thunk)))
511         (thread-specific-set! t #t)
512         (thread-start! t)
513         t))
514 
515     (define (stop! thread)
516       (thread-specific-set! thread #f)
517       (thread-join! thread))
518 
519     (define (keep-going?)
520       (thread-specific (current-thread)))
521 
522     (define count!
523       (let ((m (make-mutex))
524             (i 0))
525         (lambda ()
526           (mutex-lock! m)
527           (let ((x (+ i 1)))
528             (set! i x)
529             (mutex-unlock! m)
530             x))))
531 
532     (define (increment-forever!)
533       (let loop () (count!) (if (keep-going?) (loop))))
534 
535     (let ((t1 (spawn increment-forever!))
536           (t2 (spawn increment-forever!)))
537       (thread-sleep! 1)
538       (stop! t1)
539       (stop! t2)
540       (count!))  ==>  377290
541
542<procedure>(thread-join! thread [timeout [timeout-val]])</procedure><br>
543
544The current thread waits until the {{thread}} terminates (normally or
545not) or until the timeout is reached if {{timeout}} is supplied. If the
546timeout is reached, {{thread-join!}} returns {{timeout-val}} if it is
547supplied, otherwise a "join timeout exception" is raised. If the {{thread}}
548terminated normally, the content of the end-result field is returned,
549otherwise the content of the end-exception field is raised.
550
551
552     (let ((t (thread-start! (make-thread (lambda () (expt 2 100))))))
553       (do-something-else)
554       (thread-join! t))  ==>  1267650600228229401496703205376
555 
556     (let ((t (thread-start! (make-thread (lambda () (raise 123))))))
557       (do-something-else)
558       (with-exception-handler
559         (lambda (exc)
560           (if (uncaught-exception? exc)
561               (* 10 (uncaught-exception-reason exc))
562               99999))
563         (lambda ()
564           (+ 1 (thread-join! t)))))  ==>  1231
565 
566     (define thread-alive?
567       (let ((unique (list 'unique)))
568         (lambda (thread)
569           ; Note: this procedure raises an exception if
570           ; the thread terminated abnormally.
571           (eq? (thread-join! thread 0 unique) unique))))
572 
573     (define (wait-for-termination! thread)
574       (let ((eh (current-exception-handler)))
575         (with-exception-handler
576           (lambda (exc)
577             (if (not (or (terminated-thread-exception? exc)
578                          (uncaught-exception? exc)))
579                 (eh exc))) ; unexpected exceptions are handled by eh
580           (lambda ()
581             ; The following call to thread-join! will wait until the
582             ; thread terminates.  If the thread terminated normally
583             ; thread-join! will return normally.  If the thread
584             ; terminated abnormally then one of these two exceptions
585             ; is raised by thread-join!:
586             ;   - terminated thread exception
587             ;   - uncaught exception
588             (thread-join! thread)
589             #f)))) ; ignore result of thread-join!
590
591<procedure>(mutex? obj)</procedure><br>
592
593Returns {{#t}} if {{obj}} is a mutex, otherwise returns {{#f}}.
594
595
596     (mutex? (make-mutex))  ==>  #t
597     (mutex? 'foo)          ==>  #f
598
599<procedure>(make-mutex [name])</procedure><br>
600
601Returns a new mutex in the unlocked/not-abandoned state. The optional
602{{name}} is an arbitrary Scheme object which identifies the mutex (useful
603for debugging); it defaults to an unspecified value. The mutex's specific
604field is set to an unspecified value.
605
606
607     (make-mutex)       ==>  ''an unlocked/not-abandoned mutex''
608     (make-mutex 'foo)  ==>  ''an unlocked/not-abandoned mutex named'' foo
609
610
611<procedure>(mutex-name mutex)</procedure><br>
612
613Returns the name of the {{mutex}}.
614
615
616     (mutex-name (make-mutex 'foo))  ==>  foo
617
618
619<procedure>(mutex-specific mutex)</procedure><br>
620
621Returns the content of the {{mutex}}'s specific field.
622
623<procedure>(mutex-specific-set! mutex obj)</procedure><br>
624
625Stores {{obj}} into the {{mutex}}'s specific field. {{mutex-specific-set!}}
626returns an unspecified value.
627
628
629     (define m (make-mutex))
630     (mutex-specific-set! m "hello")  ==>  ''unspecified''
631 
632     (mutex-specific m)               ==>  "hello"
633 
634     (define (mutex-lock-recursively! mutex)
635       (if (eq? (mutex-state mutex) (current-thread))
636           (let ((n (mutex-specific mutex)))
637             (mutex-specific-set! mutex (+ n 1)))
638           (begin
639             (mutex-lock! mutex)
640             (mutex-specific-set! mutex 0))))
641 
642     (define (mutex-unlock-recursively! mutex)
643       (let ((n (mutex-specific mutex)))
644         (if (= n 0)
645             (mutex-unlock! mutex)
646             (mutex-specific-set! mutex (- n 1)))))
647
648<procedure>(mutex-state mutex)</procedure><br>
649
650Returns information about the state of the {{mutex}}. The possible results
651are:
652
653
654* '''thread T''': the {{mutex}} is in the locked/owned state and thread T is the owner of the {{mutex}}
655* '''symbol {{not-owned}}''': the {{mutex}} is in the locked/not-owned state
656* '''symbol {{abandoned}}''': the {{mutex}} is in the unlocked/abandoned state
657* '''symbol {{not-abandoned}}''': the {{mutex}} is in the unlocked/not-abandoned state
658
659
660     (mutex-state (make-mutex))  ==>  not-abandoned
661 
662     (define (thread-alive? thread)
663       (let ((mutex (make-mutex)))
664         (mutex-lock! mutex #f thread)
665         (let ((state (mutex-state mutex)))
666           (mutex-unlock! mutex) ; avoid space leak
667           (eq? state thread))))
668
669<procedure>(mutex-lock! mutex [timeout [thread]])</procedure><br>
670
671If the {{mutex}} is currently locked, the current thread waits until the
672{{mutex}} is unlocked, or until the timeout is reached if {{timeout}}
673is supplied. If the timeout is reached, {{mutex-lock!}} returns {{#f}}.
674Otherwise, the state of the {{mutex}} is changed as follows:
675
676
677* if {{thread}} is {{#f}} the {{mutex}} becomes locked/not-owned,
678* otherwise, let T be {{thread}} (or the current thread if {{thread}} is not supplied),
679** if T is terminated the {{mutex}} becomes unlocked/abandoned,
680** otherwise {{mutex}} becomes locked/owned with T as the owner.
681
682After changing the state of the {{mutex}}, an "abandoned mutex exception"
683is raised if the {{mutex}} was unlocked/abandoned before the state change,
684otherwise {{mutex-lock!}} returns {{#t}}. It is not an error if the
685{{mutex}} is owned by the current thread (but the current thread will have
686to wait).
687
688
689     ; an implementation of a mailbox object of depth one; this
690     ; implementation does not behave well in the presence of forced
691     ; thread terminations using thread-terminate! (deadlock can occur
692     ; if a thread is terminated in the middle of a put! or get! operation)
693 
694     (define (make-empty-mailbox)
695       (let ((put-mutex (make-mutex)) ; allow put! operation
696             (get-mutex (make-mutex))
697             (cell #f))
698 
699         (define (put! obj)
700           (mutex-lock! put-mutex #f #f) ; prevent put! operation
701           (set! cell obj)
702           (mutex-unlock! get-mutex)) ; allow get! operation
703 
704         (define (get!)
705           (mutex-lock! get-mutex #f #f) ; wait until object in mailbox
706           (let ((result cell))
707             (set! cell #f) ; prevent space leaks
708             (mutex-unlock! put-mutex) ; allow put! operation
709             result))
710 
711         (mutex-lock! get-mutex #f #f) ; prevent get! operation
712 
713         (lambda (msg)
714           (case msg
715             ((put!) put!)
716             ((get!) get!)
717             (else (error "unknown message"))))))
718 
719     (define (mailbox-put! m obj) ((m 'put!) obj))
720     (define (mailbox-get! m) ((m 'get!)))
721 
722     ; an alternate implementation of thread-sleep!
723 
724     (define (sleep! timeout)
725       (let ((m (make-mutex)))
726         (mutex-lock! m #f #f)
727         (mutex-lock! m timeout #f)))
728 
729     ; a procedure that waits for one of two mutexes to unlock
730 
731     (define (lock-one-of! mutex1 mutex2)
732       ; this procedure assumes that neither mutex1 or mutex2
733       ; are owned by the current thread
734       (let ((ct (current-thread))
735             (done-mutex (make-mutex)))
736         (mutex-lock! done-mutex #f #f)
737         (let ((t1 (thread-start!
738                    (make-thread
739                     (lambda ()
740                       (mutex-lock! mutex1 #f ct)
741                       (mutex-unlock! done-mutex)))))
742               (t2 (thread-start!
743                    (make-thread
744                     (lambda ()
745                       (mutex-lock! mutex2 #f ct)
746                       (mutex-unlock! done-mutex))))))
747           (mutex-lock! done-mutex #f #f)
748           (thread-terminate! t1)
749           (thread-terminate! t2)
750           (if (eq? (mutex-state mutex1) ct)
751               (begin
752                 (if (eq? (mutex-state mutex2) ct)
753                     (mutex-unlock! mutex2)) ; don't lock both
754                 mutex1)
755               mutex2))))
756
757<procedure>(mutex-unlock! mutex [condition-variable [timeout]])</procedure><br>
758
759Unlocks the {{mutex}} by making it unlocked/not-abandoned. It is not an
760error to unlock an unlocked mutex and a mutex that is owned by any thread.
761If {{condition-variable}} is supplied, the current thread is blocked
762and added to the {{condition-variable}} before unlocking {{mutex}}; the
763thread can unblock at any time but no later than when an appropriate call
764to {{condition-variable-signal!}} or {{condition-variable-broadcast!}}
765is performed (see below), and no later than the timeout (if {{timeout}}
766is supplied). If there are threads waiting to lock this {{mutex}},
767the scheduler selects a thread, the mutex becomes locked/owned or
768locked/not-owned, and the thread is unblocked. {{mutex-unlock!}} returns
769{{#f}} when the timeout is reached, otherwise it returns {{#t}}.
770
771NOTE: The reason the thread can unblock at any time (when
772{{condition-variable}} is supplied) is to allow extending this SRFI with
773primitives that force a specific blocked thread to become runnable. For
774example a primitive to interrupt a thread so that it performs a certain
775operation, whether the thread is blocked or not, may be useful to handle
776the case where the scheduler has detected a serious problem (such as a
777deadlock) and it must unblock one of the threads (such as the primordial
778thread) so that it can perform some appropriate action. After a thread
779blocked on a condition-variable has handled such an interrupt it would be
780wrong for the scheduler to return the thread to the blocked state, because
781any calls to {{condition-variable-broadcast!}} during the interrupt will
782have gone unnoticed. It is necessary for the thread to remain runnable and
783return from the call to {{mutex-unlock!}} with a result of {{#t}}.
784
785NOTE: {{mutex-unlock!}} is related to the "wait" operation on condition
786variables available in other thread systems. The main difference is that
787"wait" automatically locks {{mutex}} just after the thread is unblocked.
788This operation is not performed by {{mutex-unlock!}} and so must be
789done by an explicit call to {{mutex-lock!}}. This has the advantages
790that a different timeout and exception handler can be specified on the
791{{mutex-lock!}} and {{mutex-unlock!}} and the location of all the mutex
792operations is clearly apparent. A typical use with a condition variable is:
793
794
795     (let loop ()
796       (mutex-lock! m)
797       (if (condition-is-true?)
798           (begin
799             (do-something-when-condition-is-true)
800             (mutex-unlock! m))
801           (begin
802             (mutex-unlock! m cv)
803             (loop))))
804
805<procedure>(condition-variable? obj)</procedure><br>
806
807Returns {{#t}} if {{obj}} is a condition variable, otherwise returns
808{{#f}}.
809
810
811     (condition-variable? (make-condition-variable))  ==>  #t
812     (condition-variable? 'foo)                       ==>  #f
813
814<procedure>(make-condition-variable [name])</procedure><br>
815
816Returns a new empty condition variable. The optional {{name}} is an
817arbitrary Scheme object which identifies the condition variable (useful for
818debugging); it defaults to an unspecified value. The condition variable's
819specific field is set to an unspecified value.
820
821
822     (make-condition-variable)  ==>  ''an empty condition variable''
823
824<procedure>(condition-variable-name condition-variable)</procedure><br>
825
826Returns the name of the {{condition-variable}}.
827
828
829     (condition-variable-name (make-condition-variable 'foo))  ==>  foo
830
831<procedure>(condition-variable-specific condition-variable)</procedure><br>
832
833Returns the content of the {{condition-variable}}'s specific field.
834
835<procedure>(condition-variable-specific-set! condition-variable obj)</procedure><br>
836
837Stores {{obj}} into the {{condition-variable}}'s specific field.
838{{condition-variable-specific-set!}} returns an unspecified value.
839
840
841     (define cv (make-condition-variable))
842     (condition-variable-specific-set! cv "hello")  ==>  ''unspecified''
843 
844     (condition-variable-specific cv)               ==>  "hello"
845
846<procedure>(condition-variable-signal! condition-variable)</procedure><br>
847
848If there are threads blocked on the {{condition-variable}}, the scheduler
849selects a thread and unblocks it. {{condition-variable-signal!}} returns an
850unspecified value.
851
852
853     ; an implementation of a mailbox object of depth one; this
854     ; implementation behaves gracefully when threads are forcibly
855     ; terminated using thread-terminate! (the "abandoned mutex"
856     ; exception will be raised when a put! or get! operation is attempted
857     ; after a thread is terminated in the middle of a put! or get!
858     ; operation)
859 
860     (define (make-empty-mailbox)
861       (let ((mutex (make-mutex))
862             (put-condvar (make-condition-variable))
863             (get-condvar (make-condition-variable))
864             (full? #f)
865             (cell #f))
866 
867         (define (put! obj)
868           (mutex-lock! mutex)
869           (if full?
870               (begin
871                 (mutex-unlock! mutex put-condvar)
872                 (put! obj))
873               (begin
874                 (set! cell obj)
875                 (set! full? #t)
876                 (condition-variable-signal! get-condvar)
877                 (mutex-unlock! mutex))))
878 
879         (define (get!)
880           (mutex-lock! mutex)
881           (if (not full?)
882               (begin
883                 (mutex-unlock! mutex get-condvar)
884                 (get!))
885               (let ((result cell))
886                 (set! cell #f) ; avoid space leaks
887                 (set! full? #f)
888                 (condition-variable-signal! put-condvar)
889                 (mutex-unlock! mutex))))
890 
891         (lambda (msg)
892           (case msg
893             ((put!) put!)
894             ((get!) get!)
895             (else (error "unknown message"))))))
896 
897     (define (mailbox-put! m obj) ((m 'put!) obj))
898     (define (mailbox-get! m) ((m 'get!)))
899
900<procedure>(condition-variable-broadcast! condition-variable)</procedure><br>
901
902Unblocks all the threads blocked on the {{condition-variable}}.
903{{condition-variable-broadcast!}} returns an unspecified value.
904
905
906     (define (make-semaphore n)
907       (vector n (make-mutex) (make-condition-variable)))
908 
909     (define (semaphore-wait! sema)
910       (mutex-lock! (vector-ref sema 1))
911       (let ((n (vector-ref sema 0)))
912         (if (> n 0)
913             (begin
914               (vector-set! sema 0 (- n 1))
915               (mutex-unlock! (vector-ref sema 1)))
916             (begin
917               (mutex-unlock! (vector-ref sema 1) (vector-ref sema 2))
918               (semaphore-wait! sema))))
919 
920     (define (semaphore-signal-by! sema increment)
921       (mutex-lock! (vector-ref sema 1))
922       (let ((n (+ (vector-ref sema 0) increment)))
923         (vector-set! sema 0 n)
924         (if (> n 0)
925             (condition-variable-broadcast! (vector-ref sema 2)))
926         (mutex-unlock! (vector-ref sema 1))))
927
928<procedure>(current-time)</procedure><br>
929
930Returns the time object corresponding to the current time.
931
932
933     (current-time)  ==>  ''a time object''
934
935<procedure>(time? obj)</procedure><br>
936
937Returns {{#t}} if {{obj}} is a time object, otherwise returns {{#f}}.
938
939
940     (time? (current-time))  ==>  #t
941     (time? 123)             ==>  #f
942
943<procedure>(time->seconds time)</procedure><br>
944
945Converts the time object {{time}} into an exact or inexact real number
946representing the number of seconds elapsed since some implementation
947dependent reference point.
948
949
950     (time->seconds (current-time))  ==>  955039784.928075
951
952<procedure>(seconds->time x)</procedure><br>
953
954Converts into a time object the exact or inexact real number {{x}}
955representing the number of seconds elapsed since some implementation
956dependent reference point.
957
958
959     (seconds->time (+ 10 (time->seconds (current-time)))
960        ==>  ''a time object representing 10 seconds in the future''
961
962
963<procedure>(current-exception-handler)</procedure><br>
964
965Returns the current exception handler.
966
967
968     (current-exception-handler)  ==>  ''a procedure''
969
970<procedure>(with-exception-handler handler thunk)</procedure><br>
971
972Returns the result(s) of calling {{thunk}} with no arguments. The
973{{handler}}, which must be a procedure, is installed as the current
974exception handler in the dynamic environment in effect during the call to
975{{thunk}}.
976
977
978     (with-exception-handler
979       list
980       current-exception-handler)  ==>  ''the procedure'' list
981
982<procedure>(raise obj)</procedure><br>
983
984Calls the current exception handler with {{obj}} as the single argument.
985{{obj}} may be any Scheme object.
986
987
988     (define (f n)
989       (if (< n 0) (raise "negative arg") (sqrt n))))
990 
991     (define (g)
992       (call-with-current-continuation
993         (lambda (return)
994           (with-exception-handler
995             (lambda (exc)
996               (return
997                 (if (string? exc)
998                     (string-append "error: " exc)
999                     "unknown error")))
1000             (lambda ()
1001               (write (f 4.))
1002               (write (f -1.))
1003               (write (f 9.)))))))
1004 
1005     (g)  ==>  ''writes'' 2. ''and returns'' "error: negative arg"
1006
1007
1008<procedure>(join-timeout-exception? obj)</procedure><br>
1009
1010Returns {{#t}} if {{obj}} is a "join timeout exception" object, otherwise
1011returns {{#f}}. A join timeout exception is raised when {{thread-join!}} is
1012called, the timeout is reached and no {{timeout-val}} is supplied.
1013
1014<procedure>(abandoned-mutex-exception? obj)</procedure><br>
1015
1016Returns {{#t}} if {{obj}} is an "abandoned mutex exception" object,
1017otherwise returns {{#f}}. An abandoned mutex exception is raised when the
1018current thread locks a mutex that was owned by a thread which terminated
1019(see {{mutex-lock!}}).
1020
1021<procedure>(terminated-thread-exception? obj)</procedure><br>
1022
1023Returns {{#t}} if {{obj}} is a "terminated thread exception" object,
1024otherwise returns {{#f}}. A terminated thread exception is raised when
1025{{thread-join!}} is called and the target thread has terminated as a result
1026of a call to {{thread-terminate!}}.
1027
1028<procedure>(uncaught-exception? obj)</procedure><br>
1029
1030Returns {{#t}} if {{obj}} is an "uncaught exception" object, otherwise
1031returns {{#f}}. An uncaught exception is raised when {{thread-join!}} is
1032called and the target thread has terminated because it raised an exception
1033that called the initial exception handler of that thread.
1034
1035<procedure>(uncaught-exception-reason exc)</procedure><br>
1036
1037{{exc}} must be an "uncaught exception" object.
1038{{uncaught-exception-reason}} returns the object which was passed to the
1039initial exception handler of that thread.
1040
1041
1042---
1043Previous: [[Unit srfi-14]]
1044
1045Next: [[Unit srfi-69]]
Note: See TracBrowser for help on using the repository browser.