source: project/wiki/eggref/5/srfi-18 @ 37665

Last change on this file since 37665 was 37665, checked in by sjamaan, 4 months ago

Add 0.1.2 to srfi-18 changelog

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