Previous: Pessimistic concurrency, Up: Multithreading
Along with several useful thread synchronization abstraction facilities
built-in to Scheme48, there is also a simple and lower-level mechanism
for suspending & resuming threads. The following bindings are exported
from the threads-internal
structure.
Threads have a field for a cell that is used when the
thread is suspended. When it is ready to run, it is simply #f
.
Suspending a thread involves setting its cell to a cell accessible
outside, so the thread can later be awoken. When the thread is awoken,
its cell field and the contents of the cell are both set to #f
.
Often, objects involved in the synchronization of threads will have a
queue of thread cells. There are two specialized
operations on thread cell queues that simplify filtering out cells of
threads that have already been awoken.
These attempt to commit the current proposal. If the commit fails, they immediately return
#f
. Otherwise, they suspend the current thread.Maybe-commit-and-block
first sets the current thread's cell to cell, which should contain the current thread.Maybe-commit-and-block-on-queue
adds a cell containing the current thread to queue first. When the current thread is finally resumed, these return#t
.
Attempts to commit the current proposal. If the commit fails, this returns
#f
. Otherwise,maybe-commit-and-make-ready
awakens the specified thread[s] by clearing the thread/each thread's cell and sending a message to the relevant scheduler[s] and returns#t
. If thread-or-queue is a thread, it simply awakens that; if it is a queue, it empties the queue and awakens each thread in it.
Maybe-dequeue-thread!
returns the next thread cell's contents in the queue of thread cells thread-cell-queue. It removes cells that have been emptied, i.e. whose threads have already been awoken.Thread-queue-empty?
returns#t
if there are no cells in thread-cell-queue that contain threads, i.e. threads that are still suspended. It too removes cells that have been emptied.
For example, the definition of placeholders is presented here. Placeholders contain two fields: the
cached value (set when the placeholder is set) & a queue of threads
waiting (set to #f
when the placeholder is assigned).
(define-synchronized-record-type placeholder :placeholder (really-make-placeholder queue) (value queue) ; synchronized fields placeholder? (queue placeholder-queue set-placeholder-queue!) (value placeholder-real-value set-placeholder-value!)) (define (make-placeholder) (really-make-placeholder (make-queue))) (define (placeholder-value placeholder) ;; Set up a new proposal for the transaction. (with-new-proposal (lose) (cond ((placeholder-queue placeholder) ;; There's a queue of waiters. Attempt to commit the ;; proposal and block. We'll be added to the queue if the ;; commit succeeds; if it fails, retry. => (lambda (queue) (or (maybe-commit-and-block-on-queue queue) (lose)))))) ;; Once our thread has been awoken, the placeholder will be set. (placeholder-real-value placeholder)) (define (placeholder-set! placeholder value) ;; Set up a new proposal for the transaction. (with-new-proposal (lose) (cond ((placeholder-queue placeholder) => (lambda (queue) ;; Clear the queue, set the value field. (set-placeholder-queue! placeholder #f) (set-placeholder-value! placeholder value) ;; Attempt to commit our changes and awaken all of the ;; waiting threads. If the commit fails, retry. (if (not (maybe-commit-and-make-ready queue)) (lose)))) (else ;; Someone assigned it first. Since placeholders are ;; single-assignment cells, this is an error. (error "placeholder is already assigned" placeholder (placeholder-real-value placeholder))))))