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

Last change on this file since 36761 was 36761, checked in by svnwiki, 21 months ago

Anonymous wiki edit for IP [185.193.125.115]: Mention -:x in srfi-18 for CHICKEN 4

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