11.2.1 Events
A synchronizable event (or just event for short) works with the sync procedure to coordinate synchronization among threads. Certain kinds of objects double as events, including ports and threads. Other kinds of objects exist only for their use as events.
At any point in time, an event is either ready for synchronization, or it is not; depending on the kind of event and how it is used by other threads, an event can switch from not ready to ready (or back), at any time. If a thread synchronizes on an event when it is ready, then the event produces a particular synchronization result.
Synchronizing an event may affect the state of the event. For example, when synchronizing a semaphore, then the semaphore’s internal count is decremented, just as with semaphore-wait. For most kinds of events, however (such as a port), synchronizing does not modify the event’s state.
Racket values that act as synchronizable events include semaphores, channels, asynchronous channels, ports, TCP listeners, log receivers, threads, subprocesses, will executors, and custodian boxes. Libraries can define new synchronizable events, especially though prop:evt.
> (evt? never-evt) #t
> (evt? (make-channel)) #t
> (evt? 5) #f
When at least one evt is ready, its synchronization result (often evt itself) is returned. If multiple evts are ready, one of the evts is chosen pseudo-randomly for the result; the current-evt-pseudo-random-generator parameter sets the random-number generator that controls this choice.
> (define ch (make-channel))
> (thread (λ () (displayln (sync ch)))) #<thread>
> (channel-put ch 'hellooooo) hellooooo
Changed in version 6.1.0.3 of package base: Allow 0 arguments instead of 1 or more.
procedure
(sync/timeout timeout evt ...) → any
timeout : (or/c #f (and/c real? (not/c negative?)) (-> any)) evt : evt?
A zero value for timeout is equivalent to (lambda () #f). In either case, each evt is checked at least once before returning #f or calling timeout.
See also alarm-evt for an alternative timeout mechanism.
; times out before waking up
> (sync/timeout 0.5 (thread (λ () (sleep 1) (displayln "woke up!")))) #f
> (sync/timeout (λ () (displayln "no ready events")) never-evt) no ready events
Changed in version 6.1.0.3 of package base: Allow 1 argument instead of 2 or more.
procedure
(sync/enable-break evt ...) → any
evt : evt?
procedure
(sync/timeout/enable-break timeout evt ...) → any
timeout : (or/c #f (and/c real? (not/c negative?)) (-> any)) evt : evt?
procedure
(choice-evt evt ...) → evt?
evt : evt?
That is, an event returned by choice-evt is ready for synchronization when one or more of the evts supplied to choice-evt are ready for synchronization. If the choice event is chosen, one of its ready evts is chosen pseudo-randomly, and the synchronization result is the chosen evt’s synchronization result.
> (define ch1 (make-channel))
> (define ch2 (make-channel))
> (define either-channel (choice-evt ch1 ch2))
> (thread (λ () (displayln (sync either-channel)))) #<thread>
> (channel-put (if (> (random) 0.5) ch1 ch2) 'tuturuu) tuturuu
The call to wrap is parameterize-breaked to disable breaks initially.
> (define ch (make-channel))
> (define evt (wrap-evt ch (λ (v) (format "you've got mail: ~a" v))))
> (thread (λ () (displayln (sync evt)))) #<thread>
> (channel-put ch "Dear Alice ...") you've got mail: Dear Alice ...
procedure
(handle-evt evt handle) → handle-evt?
evt : evt? handle : (any/c ... . -> . any)
> (define msg-ch (make-channel))
> (define exit-ch (make-channel))
> (thread (λ () (let loop ([val 0]) (printf "val = ~a~n" val) (sync (handle-evt msg-ch (λ (val) (loop val))) (handle-evt exit-ch (λ (val) (displayln val))))))) val = 0
#<thread>
> (channel-put msg-ch 5) val = 5
> (channel-put msg-ch 7) val = 7
> (channel-put exit-ch 'done) done
An event guard returned by guard-evt generates an event when guard is used with sync (or whenever it is part of a choice event used with sync, etc.), where the generated event is the result of calling maker. The maker procedure may be called by sync at most once for a given call to sync, but maker may not be called if a ready event is chosen before guard is even considered.
If maker returns a non-event, then maker’s result is replaced with an event that is ready for synchronization and whose synchronization result is guard.
The NACK event becomes ready for synchronization when the event is abandoned when either some other event is chosen, the synchronizing thread is dead, or control escapes from the call to sync (even if nack-guard’s maker has not yet returned a value). If the event returned by maker is chosen, then the NACK event never becomes ready for synchronization.
If #t is supplied to maker, if breaks are disabled, if the polling thread is not terminated, and if polling the resulting event produces a synchronization result, then the event will certainly be chosen for its result.
The attempt to synchronize on evt proceeds concurrently as the attempt to synchronize on the result guard from replace-evt; despite that concurrency, if maker is called, it is called in the thread that is synchronizing on guard. Synchronization can succeed for both evt and another synchronized with guard at the same time; the single-choice guarantee of synchronization applies only to the result of maker and other events synchronized with guard.
If maker returns a non-event, then maker’s result is replaced with an event that is ready for synchronization and whose synchronization result is guard.
Added in version 6.1.0.3 of package base.
value
> (sync always-evt) #<always-evt>
> (sync/timeout 0.1 never-evt) #f
procedure
(system-idle-evt) → evt?
> (define th (thread (λ () (let loop () (loop)))))
> (sync/timeout 0.1 (system-idle-evt)) #f
> (kill-thread th)
> (sync (system-idle-evt))
> (define alarm (alarm-evt (+ (current-inexact-milliseconds) 100)))
> (sync alarm) #<alarm-evt>
procedure
(handle-evt? evt) → boolean?
evt : evt?
> (handle-evt? never-evt) #f
> (handle-evt? (handle-evt always-evt values)) #t
value
An event evt: In this case, using the structure as an event is equivalent to using evt.
A procedure proc of one argument: In this case, the structure is similar to an event generated by guard-evt, except that the would-be guard procedure proc receives the structure as an argument, instead of no arguments; also, a non-event result from proc is replaced with an event that is already ready for synchronization and whose synchronization result is the structure.
An exact, non-negative integer between 0 (inclusive) and the number of non-automatic fields in the structure type (exclusive, not counting supertype fields): The integer identifies a field in the structure, and the field must be designated as immutable. If the field contains an object or an event-generating procedure of one argument, the event or procedure is used as above. Otherwise, the structure acts as an event that is never ready.
Instances of a structure type with the prop:input-port or prop:output-port property are also synchronizable events by virtue of being a port. If the structure type has more than one of prop:evt, prop:input-port, and prop:output-port, then the prop:evt value (if any) takes precedence for determining the instance’s behavior as an event, and the prop:input-port property takes precedence over prop:output-port for synchronization.
> (define-struct wt (base val) #:property prop:evt (struct-field-index base))
> (define sema (make-semaphore))
> (sync/timeout 0 (make-wt sema #f)) #f
> (semaphore-post sema)
> (sync/timeout 0 (make-wt sema #f)) #<semaphore>
> (semaphore-post sema)
> (sync/timeout 0 (make-wt (lambda (self) (wt-val self)) sema)) #<semaphore>
> (semaphore-post sema)
> (define my-wt (make-wt (lambda (self) (wrap-evt (wt-val self) (lambda (x) self))) sema))
> (sync/timeout 0 my-wt) #<wt>
> (sync/timeout 0 my-wt) #f
parameter
→ pseudo-random-generator? (current-evt-pseudo-random-generator generator) → void? generator : pseudo-random-generator?