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

Last change on this file since 23717 was 23717, checked in by Ivan Raikov, 9 years ago

Chicken manual: merging changes from the core repository

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