When a thread is created, it is placed into the management of the current custodian and added to the current thread group. A thread can have any number of custodian managers added through thread-resume.
A thread that has not terminated can be garbage collected (see Garbage Collection) if it is unreachable and suspended or if it is unreachable and blocked on only unreachable events through functions such as semaphore-wait, semaphore-wait/enable-break, channel-put, channel-get, sync, sync/enable-break, or thread-wait. Beware, however, of a limitation on place-channel blocking; see the caveat in Places.
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.
All constant-time procedures and operations provided by Racket are thread-safe because they are atomic. For example, 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, vector-set! assigns to a vector atomically. The hash-set! procedure is not atomic, but the table is protected by a lock; see Hash Tables 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 port-commit-peeked and write-bytes-avail offer specific concurrency guarantees.
thunk : (->any) cust : custodian? = (current-custodian)
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 call-in-nested-thread dies before thunk returns, the exn:fail exception is raised in the original thread. If the original thread is killed before thunk returns, a break is queued for the nested thread.
If a break is queued for the original thread (with 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.
thd : thread? benefactor : (or/c thread? custodian? #f) = #f
If benefactor is a thread, whenever it is resumed from a suspended state in the future, then thd is also resumed. (Resuming thd may trigger the resumption of other threads that were previously attached to thd through thread-resume.)
New custodians may be added to thd’s set of managers. If benefactor is a thread, then all of the thread’s custodians are added to thd. Otherwise, benefactor is a custodian, and it is added to thd (unless the custodian is already shut down). If thd becomes managed by both a custodian and one or more of its subordinates, the redundant subordinates are removed from thd. If thd is suspended and a custodian is added, then thd is resumed only after the addition.
If benefactor is a thread, whenever it receives a new managing custodian in the future, then thd also receives the custodian. (Adding custodians to thd may trigger adding the custodians to other threads that were previously attached to thd through thread-resume.)
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.
Each thread has a mailbox through which it can receive arbitrary messages. In other words, each thread has a built-in asynchronous channel.
See also Buffered Asynchronous Channels.