racket/collects/scribblings/reference/threads.scrbl
Matthew Flatt 4d76aa2040 place channels & threads: improve GC
Allow a thread to be GCed when it is blocked on a place
channel for reading and the place channel's write end
is inaccessible.

GC is limited to threads that do not participate in cycles
of such threads, where the otherwise unerachable threads
are blocked on place channels that are reachable among the
set of threads. In other words, the GC finds the greatest
fix point (as measured by the threads to retain) instead of
least fix point --- which isn't what you want, but finding
the least fix point seems to require significant extra GC
machinery across places.

This improvement was intended to solve the same problem as
commit 7b0608c, but that case seems to run into the limitation
on cycles.
2013-03-25 05:15:24 -06:00

281 lines
12 KiB
Racket

#lang scribble/doc
@(require "mz.rkt")
@title[#:tag "threads"]{Threads}
See @secref["thread-model"] for basic information on the Racket
thread model. See also @secref["futures"].
When a thread is created, it is placed into the management of the
@tech{current custodian} and added to the current @tech{thread
group}. A thread can have any number of custodian managers added
through @racket[thread-resume].
A thread that has not terminated can be garbage collected (see
@secref["gc-model"]) if it is unreachable and suspended or if it is
unreachable and blocked on only unreachable events through functions
such as @racket[semaphore-wait], @racket[semaphore-wait/enable-break],
@racket[channel-put], @racket[channel-get], @racket[sync],
@racket[sync/enable-break], or @racket[thread-wait]. Beware, however,
of a limitation on @tech{place-channel} blocking; see the
@elemref['(caveat "place-channel-gc")]{caveat} in @secref["places"].
@margin-note{In GRacket, a handler thread for an eventspace is blocked on
an internal semaphore when its event queue is empty. Thus, the handler
thread is collectible when the eventspace is unreachable and contains
no visible windows or running timers.}
A thread can be used as a @tech{synchronizable event} (see
@secref["sync"]). A thread is @tech{ready for synchronization} when
@racket[thread-wait] would not block; @resultItself{thread}.
All constant-time procedures and operations provided by Racket are
thread-safe because they are @defterm{atomic}. For example,
@racket[set!] assigns to a variable as an atomic action with respect
to all threads, so that no thread can see a ``half-assigned''
variable. Similarly, @racket[vector-set!] assigns to a vector
atomically. The @racket[hash-set!] procedure is not atomic, but
the table is protected by a lock; see @secref["hashtables"] for more
information. Port operations are generally not atomic, but they are
thread-safe in the sense that a byte consumed by one thread from an
input port will not be returned also to another thread, and procedures
like @racket[port-commit-peeked] and @racket[write-bytes-avail] offer
specific concurrency guarantees.
@;------------------------------------------------------------------------
@section{Creating Threads}
@defproc[(thread [thunk (-> any)]) thread?]{
Calls @racket[thunk] with no arguments in a new thread of control. The
@racket[thread] procedure returns immediately with a @deftech{thread
descriptor} value. When the invocation of @racket[thunk] returns, the
thread created to invoke @racket[thunk] terminates.
}
@defproc[(thread? [v any/c]) thread?]{Returns @racket[#t] if
@racket[v] is a @tech{thread descriptor}, @racket[#f] otherwise.}
@defproc[(current-thread) thread?]{Returns the @tech{thread
descriptor} for the currently executing thread.}
@defproc[(thread/suspend-to-kill [thunk (-> any)]) thread]{
Like @racket[thread], except that ``killing'' the thread through
@racket[kill-thread] or @racket[custodian-shutdown-all] merely
suspends the thread instead of terminating it. }
@defproc[(call-in-nested-thread [thunk (->any)]
[cust custodian? (current-custodian)])
any]{
Creates a nested thread managed by @racket[cust] to execute
@racket[thunk]. (The nested thread's current custodian is inherited
from the creating thread, independent of the @racket[cust] argument.)
The current thread blocks until @racket[thunk] returns, and the result
of the @racket[call-in-nested-thread] call is the result returned by
@racket[thunk].
The nested thread's exception handler is initialized to a procedure
that jumps to the beginning of the thread and transfers the exception
to the original thread. The handler thus terminates the nested thread
and re-raises the exception in the original thread.
If the thread created by @racket[call-in-nested-thread] dies before
@racket[thunk] returns, the @exnraise[exn:fail] in the original
thread. If the original thread is killed before @racket[thunk]
returns, a break is queued for the nested thread.
If a break is queued for the original thread (with
@racket[break-thread]) while the nested thread is running, the break
is redirected to the nested thread. If a break is already queued on
the original thread when the nested thread is created, the break is
moved to the nested thread. If a break remains queued on the nested
thread when it completes, the break is moved to the original thread.}
@;------------------------------------------------------------------------
@section[#:tag "threadkill"]{Suspending, Resuming, and Killing Threads}
@defproc[(thread-suspend [thd thread?]) void?]{
Immediately suspends the execution of @racket[thd] if it is
running. If the thread has terminated or is already suspended,
@racket[thread-suspend] has no effect. The thread remains suspended
(i.e., it does not execute) until it is resumed with
@racket[thread-resume]. If the @tech{current custodian} does not
solely manage @racket[thd] (i.e., some custodian of @racket[thd]
is not the current custodian or a subordinate), the
@exnraise[exn:fail:contract], and the thread is not suspended.}
@defproc[(thread-resume [thd thread?] [benefactor (or/c thread? custodian? #f) #f]) void?]{
Resumes the execution of @racket[thd] if it is suspended and has at
least one custodian (possibly added through @racket[benefactor], as
described below). If the thread has terminated, or if the thread is
already running and @racket[benefactor] is not supplied, or if the
thread has no custodian and @racket[benefactor] is not supplied, then
@racket[thread-resume] has no effect. Otherwise, if
@racket[benefactor] is supplied, it triggers up to three
additional actions:
@itemize[
@item{If @racket[benefactor] is a thread, whenever it is resumed
from a suspended state in the future, then @racket[thd] is also
resumed. (Resuming @racket[thd] may trigger the resumption of other
threads that were previously attached to @racket[thd] through
@racket[thread-resume].)}
@item{New custodians may be added to @racket[thd]'s set of
managers. If @racket[benefactor] is a thread, then all of the
thread's custodians are added to @racket[thd]. Otherwise,
@racket[benefactor] is a custodian, and it is added to @racket[thd]
(unless the custodian is already shut down). If @racket[thd]
becomes managed by both a custodian and one or more of its
subordinates, the redundant subordinates are removed from
@racket[thd]. If @racket[thd] is suspended and a custodian is
added, then @racket[thd] is resumed only after the addition.}
@item{If @racket[benefactor] is a thread, whenever it receives a
new managing custodian in the future, then @racket[thd] also
receives the custodian. (Adding custodians to @racket[thd] may
trigger adding the custodians to other threads that were previously
attached to @racket[thd] through @racket[thread-resume].)}
]}
@defproc[(kill-thread [thd thread?]) void?]{
Terminates the specified thread immediately, or suspends the thread if
@racket[thd] was created with
@racket[thread/suspend-to-kill]. Terminating the main thread exits the
application. If @racket[thd] has already terminated,
@racket[kill-thread] does nothing. If the @tech{current custodian}
does not manage @racket[thd] (and none of its subordinates manages
@racket[thd]), the @exnraise[exn:fail:contract], and the thread is not
killed or suspended.
Unless otherwise noted, procedures provided by Racket (and GRacket) are
kill-safe and suspend-safe; that is, killing or suspending a thread
never interferes with the application of procedures in other
threads. For example, if a thread is killed while extracting a
character from an input port, the character is either completely
consumed or not consumed, and other threads can safely use the port.}
@defproc[(break-thread [thd thread?]
[kind (or/c #f 'hang-up 'terminate) #f])
void?]{
@index['("threads" "breaking")]{Registers} a break with the specified
thread, where @racket[kind] optionally indicates the kind of break to
register. If breaking is disabled in @racket[thd], the break will be
ignored until breaks are re-enabled (see @secref["breakhandler"]).}
@defproc[(sleep [secs (>=/c 0) 0]) void?]{
Causes the current thread to sleep until at least @racket[secs]
seconds have passed after it starts sleeping. A zero value for
@racket[secs] simply acts as a hint to allow other threads to
execute. The value of @racket[secs] can be a non-integer to request a
sleep duration to any precision; the precision of the actual sleep
time is unspecified.}
@defproc[(thread-running? [thd thread?]) any]{
@index['("threads" "run state")]{Returns} @racket[#t] if @racket[thd]
has not terminated and is not suspended, @racket[#f] otherwise.}
@defproc[(thread-dead? [thd thread?]) any]{
Returns @racket[#t] if @racket[thd] has terminated, @racket[#f]
otherwise.}
@;------------------------------------------------------------------------
@section[#:tag "threadsync"]{Synchronizing Thread State}
@defproc[(thread-wait [thd thread?]) void?]{
Blocks execution of the current thread until @racket[thd] has
terminated. Note that @racket[(thread-wait (current-thread))]
deadlocks the current thread, but a break can end the deadlock (if
breaking is enabled; see @secref["breakhandler"]).}
@defproc[(thread-dead-evt [thd thread?]) evt?]{
Returns a @tech{synchronizable event} (see @secref["sync"]) that is
@tech{ready for synchronization} if and only if @racket[thd] has terminated. Unlike using
@racket[thd] directly, however, a reference to the event does not
prevent @racket[thd] from being garbage collected (see
@secref["gc-model"]). For a given @racket[thd],
@racket[thread-dead-evt] always returns the same (i.e., @racket[eq?])
result .@ResultItself{thread-dead event}.}
@defproc[(thread-resume-evt [thd thread?]) evt?]{
Returns a @tech{synchronizable event} (see @secref["sync"]) that
becomes @tech{ready for synchronization} when @racket[thd] is running. (If @racket[thd] has
terminated, the event never becomes ready.) If @racket[thd] runs and
is then suspended after a call to @racket[thread-resume-evt], the
result event remains ready; after each suspend of @racket[thd] a fresh
event is generated to be returned by @racket[thread-resume-evt]. The
result of the event is @racket[thd], but if @racket[thd] is never
resumed, then reference to the event does not prevent @racket[thd]
from being garbage collected (see @secref["gc-model"]).
@ResultItself{thread-result event}.}
@defproc[(thread-suspend-evt [thd thread?]) evt?]{
Returns a @tech{synchronizable event} (see @secref["sync"]) that
becomes @tech{ready for synchronization} when @racket[thd] is suspended. (If @racket[thd] has
terminated, the event will never unblock.) If @racket[thd] is
suspended and then resumes after a call to
@racket[thread-suspend-evt], the result event remains ready; after
each resume of @racket[thd] created a fresh event to be returned by
@racket[thread-suspend-evt]. @ResultItself{thread-suspend event}.}
@;------------------------------------------------------------------------
@section[#:tag "threadmbox"]{Thread Mailboxes}
Each thread has a @defterm{mailbox} through which it can receive
arbitrary messages. In other words, each thread has a built-in
asynchronous channel.
@margin-note/ref{See also @secref["async-channel"].}
@defproc[(thread-send [thd thread?] [v any/c]
[fail-thunk (or/c (-> any) #f)
(lambda () (raise-mismatch-error ....))])
any]{
Queues @racket[v] as a message to @racket[thd] without blocking. If
the message is queued, the result is @|void-const|. If @racket[thd]
stops running---as in @racket[thread-running?]---before the message is
queued, then @racket[fail-thunk] is called (through a tail call) if it is
a procedure to produce the result, or @racket[#f] is returned if
@racket[fail-thunk] is @racket[#f].}
@defproc[(thread-receive) any/c]{
Receives and dequeues a message queued for the current thread, if
any. If no message is available, @racket[thread-receive] blocks until
one is available.}
@defproc[(thread-try-receive) any/c]{
Receives and dequeues a message queued for the current thread, if any,
or returns @racket[#f] immediately if no message is available.}
@defproc[(thread-receive-evt) evt?]{
Returns a constant @tech{synchronizable event} (see @secref["sync"])
that becomes @tech{ready for synchronization} when the synchronizing thread has a message to
receive. @ResultItself{thread-receive event}.}
@defproc[(thread-rewind-receive [lst list?]) void?]{
Pushes the elements of @racket[lst] back onto the front of the current
thread's queue. The elements are pushed one by one, so that the first
available message is the last element of @racket[lst].}