scsh-0.6/doc/src/thread.tex

256 lines
8.7 KiB
TeX

\chapter{threads}
safety (and the lack thereof)
\section{Creating and controlling threads}
\begin{protos}
\proto{spawn}{ thunk}{thread}
\proto{spawn}{ thunk name}{thread}
\proto{thread?}{ thing}{boolean}
\proto{thread-name}{ thread}{name}
\proto{thread-uid}{ thread}{integer}
\protonoresult{relinquish-timeslice}{}
\protonoresult{sleep}{ time-in-?}
\protonoresult{terminate-current-thread}{}
\end{protos}
\section{Debugging multithreaded programs}
Debugging multithreaded programs can be difficult.
As described in {section whatever}, when any thread raises an
error, Scheme~48 stops running all of the threads at that command level.
The following procedure is useful in debugging multi-threaded programs.
\begin{protos}
\protonoresult{debug-message}{ element$_0$ \ldots}
\end{protos}
\code{Debug-message} prints the elements to `\code{stderr}', followed by a
newline.
The only types of values that \code{debug-message} prints in full are small
integers (fixnums), strings, characters, symbols, boolean, and the empty list.
Values of other types are abbreviated as follows.
\begin{tabular}{ll}
pair & \code{(...)}\\
vector & \code{\#(...)}\\
procedure & \code{\#\{procedure\}}\\
record & \code{\#\{<name of record type>\}}\\
all others & \code{???}\\
\end{tabular}
The great thing about \code{debug-message} is that it bypasses Scheme~48's
I/O and thread handling.
The message appears immediately, with no delays
or errors.
\code{Debug-message} is exported by the structure \code{debug-messages}.
\section{Mutual exclusion}
locks
make-lock
lock?
obtain-lock
maybe-obtain-lock
release-lock
\begin{protos}
\proto{make-lock}{ }{lock}
\proto{lock?}{ thing}{boolean}
\protonoresult{obtain-lock}{ lock}
\proto{maybe-obtain-lock}{ lock}{boolean}
\protonoresult{release-lock}{ lock}
\end{protos}
condition variables
% these require proposals
(make-condvar [id]) -> condvar
% add condvar?
(maybe-commit-and-wait-for-condvar condvar) -> boolean
(maybe-commit-and-set-condvar! condvar value)
(condvar-has-value? condvar) -> boolean
(set-condvar-value! condvar boolean)
(condvar-value condvar) -> value
(set-condvar-value! condvar value)
placeholders
make-placeholder
placeholder?
placeholder-value
placeholder-set!
\section{Optimistic concurrency}
%add an overview
A \cvar{proposal} is a record of reads from and and writes to locations in
memory.
The \cvar{logging} operations listed below record any values read or
written in the current proposal.
A reading operation, such as \code{provisional-vector-ref}, first checks to
see if the current proposal contains a value for the relevent location.
If so, that value is returned as the result of the read.
If not, the current contents of the location are stored in the proposal and
then returned as the result of the read.
A logging write to a location stores the new value as the current contents of
the location in the current proposal; the contents of the location itself
remain unchanged.
\cvar{Committing} to a proposal verifies that any reads logged in
the proposal are still valid and, if so, performs any writes that
the proposal contains.
A logged read is valid if, at the time of the commit, the location contains
the same value it had at the time of the original read (note that this does
not mean that no change occured, simply that the value now is the same as
the value then).
If a proposal has an invalid read then the effort to commit fails no change
is made to the value of any location.
The verifications and subsequent writes to memory are performed atomically
with respect to other proposal commit attempts.
% Explain better. Add an example?
\begin{protos}
\proto{ensure-atomicity}{ thunk}{value(s)}
\protonoresult{ensure-atomicity!}{ thunk}
\end{protos}
\noindent
If there is a proposal in place
\code{ensure-atomicity} and \code{ensure-atomicity!}
simply make a (tail-recursive) call to \cvar{thunk}.
If the current proposal is \code{\#f} they create a new proposal,
install it, call \cvar{thunk}, and then try to commit to the proposal.
This process repeats, with a new proposal on each iteration, until
the commit succeeds.
\code{Ensure-atomicity} returns whatever values are returned by \cvar{thunk}
on its final invocation, while \code{ensure-atomicity!} discards any such
values and returns nothing.
\begin{protos}
\proto{provisional-car}{ pair}{value}
\proto{provisional-cdr}{ pair}{value}
\protonoresult{provisional-set-car!}{ pair value}
\protonoresult{provisional-set-cdr!}{ pair value}
\proto{provisional-cell-ref}{ cell}{value}
\protonoresult{provisional-cell-set!}{ cell value}
\proto{provisional-vector-ref}{ vector i}{value}
\protonoresult{provisional-vector-set!}{ vector i value}
\proto{provisional-string-ref}{ vector i}{char}
\protonoresult{provisional-string-set!}{ vector i char}
\proto{provisional-byte-vector-ref}{ vector i}{k}
\protonoresult{provisional-byte-vector-set!}{ vector i k}
\end{protos}
\noindent
These are all logging versions of their Scheme counterparts.
Reads are checked when the current proposal is committed and writes are
delayed until the commit succeeds.
If the current proposal is \code{\#f} these perform exactly as their Scheme
counterparts.
The following implementation of a simple counter may not function properly
when used by multiple threads.
\begin{example}
(define (make-counter)
(let ((value 0))
(lambda ()
(set! value (+ value 1))
value)))
\end{example}
Here is the same procedure using a proposal to ensure that each
increment operation happens atomically.
The value of the counter is kept in a
\link*{cell}[cell (see section~\Ref]{cells}
to allow the use of
logging operations.
\begin{example}
(define (make-counter)
(let ((value (make-cell 0)))
(lambda ()
(ensure-atomicity
(lambda ()
(let ((v (+ (provisional-cell-ref value)
1)))
(provisional-cell-set! value v)
v))))))
\end{example}
Because \code{ensure-atomicity} creates a new proposal only if there is
no existing proposal in place, multiple atomic actions can be performed
simultaneously.
The following procedure increments an arbitrary number of counters at the same
time.
This works even if the same counter appears multiple times;
\code{(step-counters! c0 c0)} would add two to the value of counter \code{c0}.
\begin{example}
(define (step-counters! . counters)
(ensure-atomicity
(lambda ()
(for-each (lambda (counter)
(counter))
counters))))
\end{example}
\begin{example}
(define-synchronized-record-type \cvar{tag} \cvar{type-name}
(\cvar{constructor-name} \cvar{field-tag} \ldots)
[(\cvar \cvar{field-tag} \ldots)]
\cvar{predicate-name}
(\cvar{field-tag} \cvar{accessor-name} [\cvar{modifier-name}])
\ldots)
\end{example}
This is the same as \code{define-record-type}
except all field reads and
writes are logged in the current proposal.
If the optional list of field tags is present then only those fields will
be logged.
\begin{protos}
\proto{atomically}{ thunk}{value(s)}
\protonoresult{atomically!}{ thunk}
\end{protos}
\noindent
\code{Atomically} and \code{atomically!} are identical
to \code{ensure-atomicity} and \code{ensure-atomicity!} except that they
always install a new proposal before calling \code{thunk}.
The current proposal is saved and then restored after \code{thunk} returns.
\code{Atomically} and \code{atomically!} are useful if \code{thunk} contains
code that should not be combined with any other operation (example?).
The following procedures give access to the low-level proposal mechanism.
\begin{protos}
\proto{make-proposal}{}{proposal}
\proto{current-proposal}{}{proposal}
\protonoresult{set-current-proposal!}{ proposal}
\proto{with-proposal}{ proposal thunk}{value \ldots}
\proto{maybe-commit}{ proposal}{boolean}
\end{protos}
\noindent
\code{Make-proposal} creates a new proposal.
\code{Current-proposal} and \code{set-current-proposal} access and set
the current thread's proposal.
It is an error to pass to \code{set-current-proposal!} a proposal that
is already in use.
\code{With-proposal} saves the current proposal, installs \cvar{proposal} as
the current proposal, and then calls \cvar{thunk}.
When \cvar{thunk} returns the saved proposal is reinstalled as the current
proposal
and the value(s) returned by \cvar{thunk} are returned.
\code{Maybe-commit} verifies that any reads logged in \cvar{proposal} are
still valid and, if so, performs any writes that \cvar{proposal} contains.
A logged read is valid if, at the time of the commit, the location read contains
the same value it had at the time of the original read (note that this does
not mean that no change occured, simply that the value now is the same as
the value then).
\code{Maybe-commit} returns \code{\#t} if the commit succeeds and \code{\#f}
if it fails.