Previous: Pessimistic concurrency, Up: Multithreading


5.6 Custom thread synchronization

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.

— procedure: maybe-commit-and-block cell –> boolean
— procedure: maybe-commit-and-block-on-queue –> boolean

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.

— procedure: maybe-commit-and-make-ready thread-or-queue –> boolean

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.

— procedure: maybe-dequeue-thread! thread-cell-queue –> thread or boolean
— procedure: thread-queue-empty? thread-cell-queue –> boolean

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))))))