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

Last change on this file since 25963 was 25963, checked in by svnwiki, 8 years ago

Anonymous wiki edit for IP [77.107.164.131]: Added documentation for thread-state.

File size: 39.3 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>(thread-state thread)</procedure><br>
570
571Returns information about the state of the {{thread}}. The possible results
572are:
573
574
575* '''symbol {{created}}''': the {{thread}} is in the created state
576* '''symbol {{ready}}''': the {{thread}} is in the ready state
577* '''symbol {{running}}''': the {{thread}} is in the running state
578* '''symbol {{blocked}}''': the {{thread}} is in the blocked state
579* '''symbol {{suspended}}''': the {{thread}} is in the suspended state
580* '''symbol {{sleeping}}''': the {{thread}} is in the sleeping state
581* '''symbol {{terminated}}''': the {{thread}} is in the terminated state
582* '''symbol {{dead}}''': the {{thread}} is in the dead state
583
584
585<procedure>(mutex? obj)</procedure><br>
586
587Returns {{#t}} if {{obj}} is a mutex, otherwise returns {{#f}}.
588
589
590     (mutex? (make-mutex))  ==>  #t
591     (mutex? 'foo)          ==>  #f
592
593<procedure>(make-mutex [name])</procedure><br>
594
595Returns a new mutex in the unlocked/not-abandoned state. The optional
596{{name}} is an arbitrary Scheme object which identifies the mutex (useful
597for debugging); it defaults to an unspecified value. The mutex's specific
598field is set to an unspecified value.
599
600
601     (make-mutex)       ==>  ''an unlocked/not-abandoned mutex''
602     (make-mutex 'foo)  ==>  ''an unlocked/not-abandoned mutex named'' foo
603
604
605<procedure>(mutex-name mutex)</procedure><br>
606
607Returns the name of the {{mutex}}.
608
609
610     (mutex-name (make-mutex 'foo))  ==>  foo
611
612
613<procedure>(mutex-specific mutex)</procedure><br>
614
615Returns the content of the {{mutex}}'s specific field.
616
617<procedure>(mutex-specific-set! mutex obj)</procedure><br>
618
619Stores {{obj}} into the {{mutex}}'s specific field. {{mutex-specific-set!}}
620returns an unspecified value.
621
622
623     (define m (make-mutex))
624     (mutex-specific-set! m "hello")  ==>  ''unspecified''
625 
626     (mutex-specific m)               ==>  "hello"
627 
628     (define (mutex-lock-recursively! mutex)
629       (if (eq? (mutex-state mutex) (current-thread))
630           (let ((n (mutex-specific mutex)))
631             (mutex-specific-set! mutex (+ n 1)))
632           (begin
633             (mutex-lock! mutex)
634             (mutex-specific-set! mutex 0))))
635 
636     (define (mutex-unlock-recursively! mutex)
637       (let ((n (mutex-specific mutex)))
638         (if (= n 0)
639             (mutex-unlock! mutex)
640             (mutex-specific-set! mutex (- n 1)))))
641
642<procedure>(mutex-state mutex)</procedure><br>
643
644Returns information about the state of the {{mutex}}. The possible results
645are:
646
647
648* '''thread T''': the {{mutex}} is in the locked/owned state and thread T is the owner of the {{mutex}}
649* '''symbol {{not-owned}}''': the {{mutex}} is in the locked/not-owned state
650* '''symbol {{abandoned}}''': the {{mutex}} is in the unlocked/abandoned state
651* '''symbol {{not-abandoned}}''': the {{mutex}} is in the unlocked/not-abandoned state
652
653
654     (mutex-state (make-mutex))  ==>  not-abandoned
655 
656     (define (thread-alive? thread)
657       (let ((mutex (make-mutex)))
658         (mutex-lock! mutex #f thread)
659         (let ((state (mutex-state mutex)))
660           (mutex-unlock! mutex) ; avoid space leak
661           (eq? state thread))))
662
663<procedure>(mutex-lock! mutex [timeout [thread]])</procedure><br>
664
665If the {{mutex}} is currently locked, the current thread waits until the
666{{mutex}} is unlocked, or until the timeout is reached if {{timeout}}
667is supplied. If the timeout is reached, {{mutex-lock!}} returns {{#f}}.
668Otherwise, the state of the {{mutex}} is changed as follows:
669
670
671* if {{thread}} is {{#f}} the {{mutex}} becomes locked/not-owned,
672* otherwise, let T be {{thread}} (or the current thread if {{thread}} is not supplied),
673** if T is terminated the {{mutex}} becomes unlocked/abandoned,
674** otherwise {{mutex}} becomes locked/owned with T as the owner.
675
676After changing the state of the {{mutex}}, an "abandoned mutex exception"
677is raised if the {{mutex}} was unlocked/abandoned before the state change,
678otherwise {{mutex-lock!}} returns {{#t}}. It is not an error if the
679{{mutex}} is owned by the current thread (but the current thread will have
680to wait).
681
682
683     ; an implementation of a mailbox object of depth one; this
684     ; implementation does not behave well in the presence of forced
685     ; thread terminations using thread-terminate! (deadlock can occur
686     ; if a thread is terminated in the middle of a put! or get! operation)
687 
688     (define (make-empty-mailbox)
689       (let ((put-mutex (make-mutex)) ; allow put! operation
690             (get-mutex (make-mutex))
691             (cell #f))
692 
693         (define (put! obj)
694           (mutex-lock! put-mutex #f #f) ; prevent put! operation
695           (set! cell obj)
696           (mutex-unlock! get-mutex)) ; allow get! operation
697 
698         (define (get!)
699           (mutex-lock! get-mutex #f #f) ; wait until object in mailbox
700           (let ((result cell))
701             (set! cell #f) ; prevent space leaks
702             (mutex-unlock! put-mutex) ; allow put! operation
703             result))
704 
705         (mutex-lock! get-mutex #f #f) ; prevent get! operation
706 
707         (lambda (msg)
708           (case msg
709             ((put!) put!)
710             ((get!) get!)
711             (else (error "unknown message"))))))
712 
713     (define (mailbox-put! m obj) ((m 'put!) obj))
714     (define (mailbox-get! m) ((m 'get!)))
715 
716     ; an alternate implementation of thread-sleep!
717 
718     (define (sleep! timeout)
719       (let ((m (make-mutex)))
720         (mutex-lock! m #f #f)
721         (mutex-lock! m timeout #f)))
722 
723     ; a procedure that waits for one of two mutexes to unlock
724 
725     (define (lock-one-of! mutex1 mutex2)
726       ; this procedure assumes that neither mutex1 or mutex2
727       ; are owned by the current thread
728       (let ((ct (current-thread))
729             (done-mutex (make-mutex)))
730         (mutex-lock! done-mutex #f #f)
731         (let ((t1 (thread-start!
732                    (make-thread
733                     (lambda ()
734                       (mutex-lock! mutex1 #f ct)
735                       (mutex-unlock! done-mutex)))))
736               (t2 (thread-start!
737                    (make-thread
738                     (lambda ()
739                       (mutex-lock! mutex2 #f ct)
740                       (mutex-unlock! done-mutex))))))
741           (mutex-lock! done-mutex #f #f)
742           (thread-terminate! t1)
743           (thread-terminate! t2)
744           (if (eq? (mutex-state mutex1) ct)
745               (begin
746                 (if (eq? (mutex-state mutex2) ct)
747                     (mutex-unlock! mutex2)) ; don't lock both
748                 mutex1)
749               mutex2))))
750
751<procedure>(mutex-unlock! mutex [condition-variable [timeout]])</procedure><br>
752
753Unlocks the {{mutex}} by making it unlocked/not-abandoned. It is not an
754error to unlock an unlocked mutex and a mutex that is owned by any thread.
755If {{condition-variable}} is supplied, the current thread is blocked
756and added to the {{condition-variable}} before unlocking {{mutex}}; the
757thread can unblock at any time but no later than when an appropriate call
758to {{condition-variable-signal!}} or {{condition-variable-broadcast!}}
759is performed (see below), and no later than the timeout (if {{timeout}}
760is supplied). If there are threads waiting to lock this {{mutex}},
761the scheduler selects a thread, the mutex becomes locked/owned or
762locked/not-owned, and the thread is unblocked. {{mutex-unlock!}} returns
763{{#f}} when the timeout is reached, otherwise it returns {{#t}}.
764
765NOTE: The reason the thread can unblock at any time (when
766{{condition-variable}} is supplied) is to allow extending this SRFI with
767primitives that force a specific blocked thread to become runnable. For
768example a primitive to interrupt a thread so that it performs a certain
769operation, whether the thread is blocked or not, may be useful to handle
770the case where the scheduler has detected a serious problem (such as a
771deadlock) and it must unblock one of the threads (such as the primordial
772thread) so that it can perform some appropriate action. After a thread
773blocked on a condition-variable has handled such an interrupt it would be
774wrong for the scheduler to return the thread to the blocked state, because
775any calls to {{condition-variable-broadcast!}} during the interrupt will
776have gone unnoticed. It is necessary for the thread to remain runnable and
777return from the call to {{mutex-unlock!}} with a result of {{#t}}.
778
779NOTE: {{mutex-unlock!}} is related to the "wait" operation on condition
780variables available in other thread systems. The main difference is that
781"wait" automatically locks {{mutex}} just after the thread is unblocked.
782This operation is not performed by {{mutex-unlock!}} and so must be
783done by an explicit call to {{mutex-lock!}}. This has the advantages
784that a different timeout and exception handler can be specified on the
785{{mutex-lock!}} and {{mutex-unlock!}} and the location of all the mutex
786operations is clearly apparent. A typical use with a condition variable is:
787
788
789     (let loop ()
790       (mutex-lock! m)
791       (if (condition-is-true?)
792           (begin
793             (do-something-when-condition-is-true)
794             (mutex-unlock! m))
795           (begin
796             (mutex-unlock! m cv)
797             (loop))))
798
799<procedure>(condition-variable? obj)</procedure><br>
800
801Returns {{#t}} if {{obj}} is a condition variable, otherwise returns
802{{#f}}.
803
804
805     (condition-variable? (make-condition-variable))  ==>  #t
806     (condition-variable? 'foo)                       ==>  #f
807
808<procedure>(make-condition-variable [name])</procedure><br>
809
810Returns a new empty condition variable. The optional {{name}} is an
811arbitrary Scheme object which identifies the condition variable (useful for
812debugging); it defaults to an unspecified value. The condition variable's
813specific field is set to an unspecified value.
814
815
816     (make-condition-variable)  ==>  ''an empty condition variable''
817
818<procedure>(condition-variable-name condition-variable)</procedure><br>
819
820Returns the name of the {{condition-variable}}.
821
822
823     (condition-variable-name (make-condition-variable 'foo))  ==>  foo
824
825<procedure>(condition-variable-specific condition-variable)</procedure><br>
826
827Returns the content of the {{condition-variable}}'s specific field.
828
829<procedure>(condition-variable-specific-set! condition-variable obj)</procedure><br>
830
831Stores {{obj}} into the {{condition-variable}}'s specific field.
832{{condition-variable-specific-set!}} returns an unspecified value.
833
834
835     (define cv (make-condition-variable))
836     (condition-variable-specific-set! cv "hello")  ==>  ''unspecified''
837 
838     (condition-variable-specific cv)               ==>  "hello"
839
840<procedure>(condition-variable-signal! condition-variable)</procedure><br>
841
842If there are threads blocked on the {{condition-variable}}, the scheduler
843selects a thread and unblocks it. {{condition-variable-signal!}} returns an
844unspecified value.
845
846
847     ; an implementation of a mailbox object of depth one; this
848     ; implementation behaves gracefully when threads are forcibly
849     ; terminated using thread-terminate! (the "abandoned mutex"
850     ; exception will be raised when a put! or get! operation is attempted
851     ; after a thread is terminated in the middle of a put! or get!
852     ; operation)
853 
854     (define (make-empty-mailbox)
855       (let ((mutex (make-mutex))
856             (put-condvar (make-condition-variable))
857             (get-condvar (make-condition-variable))
858             (full? #f)
859             (cell #f))
860 
861         (define (put! obj)
862           (mutex-lock! mutex)
863           (if full?
864               (begin
865                 (mutex-unlock! mutex put-condvar)
866                 (put! obj))
867               (begin
868                 (set! cell obj)
869                 (set! full? #t)
870                 (condition-variable-signal! get-condvar)
871                 (mutex-unlock! mutex))))
872 
873         (define (get!)
874           (mutex-lock! mutex)
875           (if (not full?)
876               (begin
877                 (mutex-unlock! mutex get-condvar)
878                 (get!))
879               (let ((result cell))
880                 (set! cell #f) ; avoid space leaks
881                 (set! full? #f)
882                 (condition-variable-signal! put-condvar)
883                 (mutex-unlock! mutex))))
884 
885         (lambda (msg)
886           (case msg
887             ((put!) put!)
888             ((get!) get!)
889             (else (error "unknown message"))))))
890 
891     (define (mailbox-put! m obj) ((m 'put!) obj))
892     (define (mailbox-get! m) ((m 'get!)))
893
894<procedure>(condition-variable-broadcast! condition-variable)</procedure><br>
895
896Unblocks all the threads blocked on the {{condition-variable}}.
897{{condition-variable-broadcast!}} returns an unspecified value.
898
899
900     (define (make-semaphore n)
901       (vector n (make-mutex) (make-condition-variable)))
902 
903     (define (semaphore-wait! sema)
904       (mutex-lock! (vector-ref sema 1))
905       (let ((n (vector-ref sema 0)))
906         (if (> n 0)
907             (begin
908               (vector-set! sema 0 (- n 1))
909               (mutex-unlock! (vector-ref sema 1)))
910             (begin
911               (mutex-unlock! (vector-ref sema 1) (vector-ref sema 2))
912               (semaphore-wait! sema))))
913 
914     (define (semaphore-signal-by! sema increment)
915       (mutex-lock! (vector-ref sema 1))
916       (let ((n (+ (vector-ref sema 0) increment)))
917         (vector-set! sema 0 n)
918         (if (> n 0)
919             (condition-variable-broadcast! (vector-ref sema 2)))
920         (mutex-unlock! (vector-ref sema 1))))
921
922<procedure>(current-time)</procedure><br>
923
924Returns the time object corresponding to the current time.
925
926
927     (current-time)  ==>  ''a time object''
928
929<procedure>(time? obj)</procedure><br>
930
931Returns {{#t}} if {{obj}} is a time object, otherwise returns {{#f}}.
932
933
934     (time? (current-time))  ==>  #t
935     (time? 123)             ==>  #f
936
937<procedure>(time->seconds time)</procedure><br>
938
939Converts the time object {{time}} into an exact or inexact real number
940representing the number of seconds elapsed since some implementation
941dependent reference point.
942
943
944     (time->seconds (current-time))  ==>  955039784.928075
945
946<procedure>(seconds->time x)</procedure><br>
947
948Converts into a time object the exact or inexact real number {{x}}
949representing the number of seconds elapsed since some implementation
950dependent reference point.
951
952
953     (seconds->time (+ 10 (time->seconds (current-time)))
954        ==>  ''a time object representing 10 seconds in the future''
955
956
957<procedure>(current-exception-handler)</procedure><br>
958
959Returns the current exception handler.
960
961
962     (current-exception-handler)  ==>  ''a procedure''
963
964<procedure>(with-exception-handler handler thunk)</procedure><br>
965
966Returns the result(s) of calling {{thunk}} with no arguments. The
967{{handler}}, which must be a procedure, is installed as the current
968exception handler in the dynamic environment in effect during the call to
969{{thunk}}.
970
971
972     (with-exception-handler
973       list
974       current-exception-handler)  ==>  ''the procedure'' list
975
976<procedure>(raise obj)</procedure><br>
977
978Calls the current exception handler with {{obj}} as the single argument.
979{{obj}} may be any Scheme object.
980
981
982     (define (f n)
983       (if (< n 0) (raise "negative arg") (sqrt n))))
984 
985     (define (g)
986       (call-with-current-continuation
987         (lambda (return)
988           (with-exception-handler
989             (lambda (exc)
990               (return
991                 (if (string? exc)
992                     (string-append "error: " exc)
993                     "unknown error")))
994             (lambda ()
995               (write (f 4.))
996               (write (f -1.))
997               (write (f 9.)))))))
998 
999     (g)  ==>  ''writes'' 2. ''and returns'' "error: negative arg"
1000
1001
1002<procedure>(join-timeout-exception? obj)</procedure><br>
1003
1004Returns {{#t}} if {{obj}} is a "join timeout exception" object, otherwise
1005returns {{#f}}. A join timeout exception is raised when {{thread-join!}} is
1006called, the timeout is reached and no {{timeout-val}} is supplied.
1007
1008<procedure>(abandoned-mutex-exception? obj)</procedure><br>
1009
1010Returns {{#t}} if {{obj}} is an "abandoned mutex exception" object,
1011otherwise returns {{#f}}. An abandoned mutex exception is raised when the
1012current thread locks a mutex that was owned by a thread which terminated
1013(see {{mutex-lock!}}).
1014
1015<procedure>(terminated-thread-exception? obj)</procedure><br>
1016
1017Returns {{#t}} if {{obj}} is a "terminated thread exception" object,
1018otherwise returns {{#f}}. A terminated thread exception is raised when
1019{{thread-join!}} is called and the target thread has terminated as a result
1020of a call to {{thread-terminate!}}.
1021
1022<procedure>(uncaught-exception? obj)</procedure><br>
1023
1024Returns {{#t}} if {{obj}} is an "uncaught exception" object, otherwise
1025returns {{#f}}. An uncaught exception is raised when {{thread-join!}} is
1026called and the target thread has terminated because it raised an exception
1027that called the initial exception handler of that thread.
1028
1029<procedure>(uncaught-exception-reason exc)</procedure><br>
1030
1031{{exc}} must be an "uncaught exception" object.
1032{{uncaught-exception-reason}} returns the object which was passed to the
1033initial exception handler of that thread.
1034
1035
1036---
1037Previous: [[Unit srfi-14]]
1038
1039Next: [[Unit srfi-69]]
Note: See TracBrowser for help on using the repository browser.