967 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			967 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| % Copyright 2005-2017 Cisco Systems, Inc.
 | |
| % 
 | |
| % Licensed under the Apache License, Version 2.0 (the "License");
 | |
| % you may not use this file except in compliance with the License.
 | |
| % You may obtain a copy of the License at
 | |
| % 
 | |
| % http://www.apache.org/licenses/LICENSE-2.0
 | |
| % 
 | |
| % Unless required by applicable law or agreed to in writing, software
 | |
| % distributed under the License is distributed on an "AS IS" BASIS,
 | |
| % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| % See the License for the specific language governing permissions and
 | |
| % limitations under the License.
 | |
| \chapter{Thread System\label{CHPTTHREADS}}
 | |
| 
 | |
| \index{threads}This chapter describes the \emph{Chez Scheme} thread-system procedures
 | |
| and syntactic forms.
 | |
| With the exception of locks, locked increment, and locked decrement,
 | |
| the features of the thread system are implemented on top of the Posix
 | |
| thread system (pthreads) on non-Windows-based system and directly using
 | |
| the Windows API on Windows-based systems.
 | |
| Consult the appropriate documentation on your system for basic details
 | |
| of thread creation and interaction.
 | |
| 
 | |
| Most primitive Scheme procedures are \index{thread-safe primitives}\emph{thread-safe}, meaning
 | |
| that they can be called concurrently from multiple threads.
 | |
| This includes allocation operations like \var{cons} and \scheme{make-string},
 | |
| accessors like \scheme{car} and \scheme{vector-ref},
 | |
| numeric operators like \scheme{+} and \scheme{sqrt}, and nondestructive
 | |
| higher-level primitive operators like \scheme{append} and \scheme{map}.
 | |
| 
 | |
| Simple mutation operators, like \scheme{set-car!}, \scheme{vector-set!},
 | |
| and record field mutators are thread-safe.
 | |
| Likewise, assignments to local variables, including assignments to
 | |
| (unexported) library and top-level program variables are thread-safe.
 | |
| 
 | |
| Other destructive operators are thread safe only if they are used to
 | |
| operate on different objects from those being read or modified by other
 | |
| threads.
 | |
| For example, assignments to global variables are thread-safe only as
 | |
| long as one thread does not assign the same variable another thread
 | |
| references or assigns.
 | |
| Similarly, \scheme{putprop} can be called in one thread while another
 | |
| concurrently calls \scheme{putprop} or \scheme{getprop} if the symbols
 | |
| whose property lists are being modified or accessed differ.
 | |
| 
 | |
| In this context, most I/O operations should be considered destructive,
 | |
| since they might modify a port's internal structure; see also
 | |
| Section~\ref{SECTTHREADSBUFFEREDIO} for information on buffered ports.
 | |
| 
 | |
| Use of operators that are not thread-safe without proper synchronization
 | |
| can corrupt the objects upon which they operate.
 | |
| This corruption can lead to incorrect behavior, memory faults, and even
 | |
| unrecoverable errors that cause the system to abort.
 | |
| 
 | |
| The compiler and interpreter are thread-safe to the extent that user code
 | |
| evaluated during the compilation and evaluation process is thread-safe or
 | |
| properly synchronized.
 | |
| Thus, two or more threads
 | |
| can call any of the compiler or interpreter entry points, i.e.,
 | |
| \scheme{compile}, \scheme{compile-file}, \scheme{compile-program}, \scheme{compile-script},
 | |
| \scheme{compile-port}, or \scheme{interpret} at the same time.
 | |
| Naturally, the object-file targets of two file compilation operations that
 | |
| run at the same time should be different.
 | |
| The same is true for \scheme{eval} and \scheme{load} as long as
 | |
| the default evaluator is used or is set explicitly to \scheme{compile},
 | |
| \scheme{interpret}, or some other thread-safe evaluator.
 | |
| 
 | |
| One restriction should be observed when one of multiple threads creates or
 | |
| loads compiled code, however, which is that only that thread or
 | |
| subsequently created children, or children of subsequently created
 | |
| children, etc., should run the code.
 | |
| This is because multiple-processor systems upon which threaded code may
 | |
| run might not guarantee that the data and instruction caches are
 | |
| synchronized across processors.
 | |
| 
 | |
| \section{Thread Creation}
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \noskipentryheader
 | |
| \formdef{fork-thread}{\categoryprocedure}{(fork-thread \var{thunk})}
 | |
| \returns a thread object
 | |
| \listlibraries
 | |
| \endnoskipentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{thunk} must be a procedure that accepts zero arguments.
 | |
| 
 | |
| \scheme{fork-thread} invokes \var{thunk} in a new thread and returns
 | |
| a thread object.
 | |
| 
 | |
| Nothing can be done with the thread object returned by
 | |
| \scheme{fork-thread}, other than to print it.
 | |
| 
 | |
| Threads created by foreign code using some means other than
 | |
| \scheme{fork-thread} must call \scheme{Sactivate_thread}
 | |
| (Section~\ref{SECTFOREIGNCLIB}) before touching any Scheme data
 | |
| or calling any Scheme procedures.
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{thread?}{\categoryprocedure}{(thread? \var{obj})}
 | |
| \returns \scheme{#t} if \var{obj} is a thread object, \scheme{#f} otherwise
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{get-thread-id}{\categoryprocedure}{(get-thread-id)}
 | |
| \returns the thread id of the current thread
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| The thread id is a thread number assigned by thread id, and has no
 | |
| relationship to the process id returned by
 | |
| \index{\scheme{get-process-id}}\scheme{get-process-id}, which is the same
 | |
| in all threads.
 | |
| 
 | |
| 
 | |
| \section{Mutexes}
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \noskipentryheader
 | |
| \formdef{make-mutex}{\categoryprocedure}{(make-mutex)}
 | |
| \formdef{make-mutex}{\categoryprocedure}{(make-mutex \var{name})}
 | |
| \returns a new mutex object
 | |
| \listlibraries
 | |
| \endnoskipentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{name}, if supplied, must be a symbol which identifies the mutex, or
 | |
| \scheme{#f} for no name. The name is printed every time the mutex is
 | |
| printed, which is useful for debugging.
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{mutex?}{\categoryprocedure}{(mutex? \var{obj})}
 | |
| \returns \scheme{#t} if \var{obj} is a mutex, \scheme{#f} otherwise
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{mutex-acquire}{\categoryprocedure}{(mutex-acquire \var{mutex})}
 | |
| \formdef{mutex-acquire}{\categoryprocedure}{(mutex-acquire \var{mutex} \var{block?})}
 | |
| \returns see below
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{mutex} must be a mutex.
 | |
| 
 | |
| \var{mutex-acquire} acquires the mutex identified by \var{mutex}.
 | |
| The optional boolean argument \var{block?} defaults to
 | |
| \scheme{#t} and specifies whether the thread should block
 | |
| waiting for the mutex.
 | |
| If \var{block?} is omitted or is true, the thread
 | |
| blocks until the mutex has been acquired, and an unspecified
 | |
| value is returned.
 | |
| 
 | |
| If \scheme{block?} is false and the mutex currently belongs
 | |
| to a different thread, the current thread does not block.
 | |
| Instead, \scheme{mutex-acquire} returns
 | |
| immediately with the value \scheme{#f} to
 | |
| indicate that the mutex is not available.
 | |
| If \var{block?} is false and the mutex is successfully
 | |
| acquired, \scheme{mutex-acquire} returns \scheme{#t}.
 | |
| 
 | |
| Mutexes are \emph{recursive} in Posix threads terminology, which
 | |
| means that the calling thread can use \scheme{mutex-acquire} to
 | |
| (re)acquire a mutex it already has.
 | |
| In this case, an equal number of \scheme{mutex-release} calls
 | |
| is necessary to release the mutex.
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{mutex-release}{\categoryprocedure}{(mutex-release \var{mutex})}
 | |
| \returns unspecified
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{mutex} must be a mutex.
 | |
| 
 | |
| \scheme{mutex-release} releases the mutex identified by \var{mutex}.
 | |
| Unpredictable behavior results if the mutex is not owned by the
 | |
| calling thread.
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{with-mutex}{\categorysyntax}{(with-mutex \var{mutex} \var{body_1} \var{body_2} \dots)}
 | |
| \returns the values of the body \scheme{\var{body_1} \var{body_2} \dots}
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| \noindent
 | |
| \scheme{with-mutex} evaluates the expression \var{mutex}, which must
 | |
| evaluate to a mutex, acquires the mutex, evaluates the body
 | |
| \scheme{\var{body_1} \var{body_2} \dots}, and releases the mutex.
 | |
| The mutex is released whether the body returns normally or
 | |
| via a control operation (that is, throw to a continuation, perhaps because
 | |
| of an error) that results in
 | |
| a nonlocal exit from the \scheme{with-mutex} form.
 | |
| If control subsequently returns to the body via a
 | |
| continuation invocation, the mutex is reacquired.
 | |
| 
 | |
| Using \scheme{with-mutex} is generally more convenient and safer than using
 | |
| \scheme{mutex-acquire} and \scheme{mutex-release} directly.
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{mutex-name}{\categoryprocedure}{(mutex-name \var{mutex})}
 | |
| \returns the name associated with \var{mutex}, if any; otherwise \scheme{#f}
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{mutex} must be a mutex.
 | |
| 
 | |
| \section{Conditions}
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \noskipentryheader
 | |
| \formdef{make-condition}{\categoryprocedure}{(make-condition)}
 | |
| \formdef{make-condition}{\categoryprocedure}{(make-condition \var{name})}
 | |
| \returns a new condition object
 | |
| \listlibraries
 | |
| \endnoskipentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{name}, if supplied, must be a symbol which identifies the condition
 | |
| object, or \scheme{#f} for no name. The name is printed every time the
 | |
| condition is printed, which is useful for debugging.
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{thread-condition?}{\categoryprocedure}{(thread-condition? \var{obj})}
 | |
| \returns \scheme{#t} if \var{obj} is a condition object, \scheme{#f} otherwise
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{condition-wait}{\categoryprocedure}{(condition-wait \var{cond} \var{mutex})}
 | |
| \formdef{condition-wait}{\categoryprocedure}{(condition-wait \var{cond} \var{mutex} \var{timeout})}
 | |
| \returns \scheme{#t} if the calling thread was awakened by the condition, \scheme{#f} if the calling thread timed out waiting
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{cond} must be a condition object, and
 | |
| \var{mutex} must be a mutex.
 | |
| The optional argument \var{timeout} is a time record of type
 | |
| \scheme{time-duration} or \scheme{time-utc}, or \scheme{#f} for no
 | |
| timeout. It defaults to \scheme{#f}.
 | |
| 
 | |
| \scheme{condition-wait} waits up to the specified \var{timeout} for
 | |
| the condition identified by the condition object \var{cond}.
 | |
| The calling thread must have acquired the mutex identified by the mutex
 | |
| \var{mutex} at the time \scheme{condition-wait} is
 | |
| called.
 | |
| \var{mutex} is released as a side effect of the call to
 | |
| \scheme{condition-wait}.
 | |
| When a thread is later released from the condition variable by one of
 | |
| the procedures described below or the timeout expires, \var{mutex} is
 | |
| reacquired and \scheme{condition-wait} returns.
 | |
| 
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{condition-signal}{\categoryprocedure}{(condition-signal \var{cond})}
 | |
| \returns unspecified
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{cond} must be a condition object.
 | |
| 
 | |
| \scheme{condition-signal} releases one of the threads waiting for the
 | |
| condition identified by \var{cond}.
 | |
| 
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{condition-broadcast}{\categoryprocedure}{(condition-broadcast \var{cond})}
 | |
| \returns unspecified
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{cond} must be a condition object.
 | |
| 
 | |
| \scheme{condition-broadcast} releases all of the threads waiting for the
 | |
| condition identified by \var{cond}.
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \entryheader
 | |
| \formdef{condition-name}{\categoryprocedure}{(condition-name \var{condition})}
 | |
| \returns the name associated with \var{condition}, if any; otherwise \scheme{#f}
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| \noindent
 | |
| \var{condition} must be a condition.
 | |
| 
 | |
| \section{Locks\label{SECTTHREADLOCKS}}
 | |
| 
 | |
| \index{locks}%
 | |
| Locks are more primitive but more flexible and efficient than mutexes
 | |
| and can be used in situations where the added mutex functionality
 | |
| is not needed or desired.
 | |
| They can also be used independently of the thread system
 | |
| (including in nonthreaded versions of {\ChezScheme})
 | |
| to synchronize operations running in separate Scheme processes
 | |
| as long as the lock is allocated in memory shared by the processes.
 | |
| 
 | |
| A lock is simply a word-sized integer, i.e., an \scheme{iptr} or
 | |
| \scheme{uptr} foreign type (Section~\ref{SECTFOREIGNDATA}) with the native
 | |
| endianness of the target machine, possibly part of a larger structure
 | |
| defined using \scheme{define-ftype} (page~\pageref{defn:define-ftype}).
 | |
| It must be explicitly allocated in memory that resides outside the Scheme
 | |
| heap and, when appropriate, explicitly deallocated.
 | |
| When just threads are involved (i.e., when multiple processes are not 
 | |
| involved), the memory can be allocated via \scheme{foreign-alloc}.
 | |
| When multiple processes are involved, the lock should be allocated in
 | |
| some area shared by the processes that will interact with the lock.
 | |
| 
 | |
| Once initialized using \scheme{ftype-init-lock!}, a process or thread
 | |
| can attempt to lock the lock via \scheme{ftype-lock!} or \scheme{ftype-spin-lock!}.
 | |
| Once the lock has been locked and before it is unlocked, further
 | |
| attempts to lock the lock fail, even by the process or thread that
 | |
| most recently locked it.
 | |
| Locks can be unlocked, via \scheme{ftype-unlock!}, by any process or thread,
 | |
| not just by the process or thread that most recently locked the lock.
 | |
| 
 | |
| The lock mechanism provides little structure, and mistakes
 | |
| in allocation and use can lead to memory faults, deadlocks,
 | |
| and other problems.
 | |
| Thus, it is usually advisable to use locks only as part of a
 | |
| higher-level abstraction that ensures locks are used in a
 | |
| disciplined manner.
 | |
| 
 | |
| \schemedisplay
 | |
| (define lock
 | |
|   (make-ftype-pointer uptr
 | |
|     (foreign-alloc (ftype-sizeof uptr))))
 | |
| 
 | |
| (ftype-init-lock! uptr () lock)
 | |
| (ftype-lock! uptr () lock) ;=> #t
 | |
| (ftype-lock! uptr () lock) ;=> #f
 | |
| (ftype-unlock! uptr () lock)
 | |
| (ftype-spin-lock! uptr () lock)
 | |
| (ftype-lock! uptr () lock) ;=> #f
 | |
| (ftype-unlock! uptr () lock)
 | |
| \endschemedisplay
 | |
| 
 | |
| \entryheader
 | |
| \formdef{ftype-init-lock!}{\categorysyntax}{(ftype-init-lock! \var{ftype-name} (\var{a} ...) \var{fptr-expr})}
 | |
| \formdef{ftype-init-lock!}{\categorysyntax}{(ftype-init-lock! \var{ftype-name} (\var{a} ...) \var{fptr-expr} \var{index})}
 | |
| \returns unspecified
 | |
| \formdef{ftype-lock!}{\categorysyntax}{(ftype-lock! \var{ftype-name} (\var{a} ...) \var{fptr-expr})}
 | |
| \formdef{ftype-lock!}{\categorysyntax}{(ftype-lock! \var{ftype-name} (\var{a} ...) \var{fptr-expr} \var{index})}
 | |
| \returns \scheme{#t} if the lock is not already locked, \scheme{#f} otherwise
 | |
| \formdef{ftype-spin-lock!}{\categorysyntax}{(ftype-spin-lock! \var{ftype-name} (\var{a} ...) \var{fptr-expr})}
 | |
| \formdef{ftype-spin-lock!}{\categorysyntax}{(ftype-spin-lock! \var{ftype-name} (\var{a} ...) \var{fptr-expr} \var{index})}
 | |
| \returns unspecified
 | |
| \formdef{ftype-unlock!}{\categorysyntax}{(ftype-unlock! \var{ftype-name} (\var{a} ...) \var{fptr-expr})}
 | |
| \formdef{ftype-unlock!}{\categorysyntax}{(ftype-unlock! \var{ftype-name} (\var{a} ...) \var{fptr-expr} \var{index})}
 | |
| \returns unspecified
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| Each of these has a syntax like and behaves similarly to
 | |
| \scheme{ftype-set!} (page~\pageref{defn:ftype-set!}), though with an implicit
 | |
| \var{val-expr}.
 | |
| In particular, the restrictions on and handling of \var{fptr-expr}
 | |
| and the accessors \scheme{\var{a} \dots} is similar, with one important
 | |
| restriction: the field specified by the last accessor, upon which
 | |
| the form operates,  must be a word-size integer, i.e., an
 | |
| \scheme{iptr}, \scheme{uptr}, or the equivalent, with the native
 | |
| endianness.
 | |
| 
 | |
| \scheme{ftype-init-lock!} should be used to initialize the lock prior
 | |
| to the use of any of the other operators; if this is not done, the
 | |
| behavior of the other operators is undefined.
 | |
| 
 | |
| \scheme{ftype-lock!} can be used to lock the lock.
 | |
| If it finds the lock unlocked at the time of the operation, it locks
 | |
| the lock and returns \scheme{#t}; if it finds the lock already locked,
 | |
| it returns \scheme{#f} without changing the lock.
 | |
| 
 | |
| \scheme{ftype-spin-lock!} can also be used to lock the lock.
 | |
| If it finds the lock unlocked at the time of the operation, it locks the
 | |
| lock and returns; if it finds the lock already locked, it waits until
 | |
| the lock is unlocked, then locks the lock and returns.
 | |
| If no other thread or process unlocks the lock, the operation does
 | |
| not return and cannot be interrupted by normal means, including by the
 | |
| storage manager for the purpose of initiating a garbage collection.
 | |
| There are also no guarantees of fairness, so a process might hang
 | |
| indefinitely even if other processes are actively locking and unlocking
 | |
| the lock.
 | |
| 
 | |
| \scheme{ftype-unlock!} is used to unlock a lock.
 | |
| If it finds the lock locked, it unlocks the lock and returns.
 | |
| Otherwise, it returns without changing the lock.
 | |
| 
 | |
| \section{Locked increment and decrement\label{SECTTHREADLOCKEDINCRDECR}}
 | |
| 
 | |
| The locked operations described here can be used when just an atomic
 | |
| increment or decrement is required.
 | |
| 
 | |
| \entryheader
 | |
| \formdef{ftype-locked-incr!}{\categorysyntax}{(ftype-locked-incr! \var{ftype-name} (\var{a} ...) \var{fptr-expr})}
 | |
| \formdef{ftype-locked-incr!}{\categorysyntax}{(ftype-locked-incr! \var{ftype-name} (\var{a} ...) \var{fptr-expr} \var{index})}
 | |
| \returns \scheme{#t} if the updated value is 0, \scheme{#f} otherwise
 | |
| \formdef{ftype-locked-decr!}{\categorysyntax}{(ftype-locked-decr! \var{ftype-name} (\var{a} ...) \var{fptr-expr})}
 | |
| \formdef{ftype-locked-decr!}{\categorysyntax}{(ftype-locked-decr! \var{ftype-name} (\var{a} ...) \var{fptr-expr} \var{index})}
 | |
| \returns \scheme{#t} if the updated value is 0, \scheme{#f} otherwise
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| Each of these has a syntax like and behaves similarly to
 | |
| \scheme{ftype-set!} (page~\pageref{defn:ftype-set!}), though with an implicit
 | |
| \var{val-expr}.
 | |
| In particular, the restrictions on and handling of \var{fptr-expr}
 | |
| and the accessors \scheme{\var{a} \dots} is similar, with one important
 | |
| restriction: the field specified by the last accessor, upon which
 | |
| the form operates,  must be a word-size integer, i.e., an
 | |
| \scheme{iptr}, \scheme{uptr}, or the equivalent, with the native
 | |
| endianness.
 | |
| 
 | |
| \scheme{ftype-locked-incr!} atomically reads the value of the specified
 | |
| field, adds $1$ to the value, and writes the new value back into the
 | |
| field.
 | |
| Similarly, \scheme{ftype-locked-decr!} atomically reads the value of
 | |
| the specified field, subtracts $1$ from the value, and writes the new
 | |
| value back into the field.
 | |
| Both return \scheme{#t} if the new value is 0, otherwise \scheme{#f}.
 | |
| 
 | |
| \section{Reference counting with ftype guardians\label{SECTTHREADFTYPEGUARDIANS}}
 | |
| 
 | |
| \index{\scheme{ftype-guardian}}%
 | |
| Applications that manage memory outside the Scheme heap can leverage
 | |
| the Scheme storage management system to help perform reference
 | |
| counting via \emph{ftype guardians}.
 | |
| In a reference-counted memory management system, each object holds
 | |
| a count of pointers to it.
 | |
| The count is incremented when a new pointer is created and decremented
 | |
| when a pointer is dropped.
 | |
| When the count reaches zero, the object is no longer needed and the
 | |
| memory it formerly occupied can be made available for some other
 | |
| purpose.
 | |
| 
 | |
| Ftype guardians are similar to guardians created by
 | |
| \index{\scheme{make-guardian}}\scheme{make-guardian}
 | |
| (Section~\ref{SECTGUARDWEAKPAIRS}).
 | |
| The \index{\scheme{guardian?}}\scheme{guardian?} procedure returns
 | |
| true for both, and the
 | |
| \index{\scheme{unregister-guardian}}\scheme{unregister-guardian}
 | |
| procedure can be used to unregister objects registered with either.
 | |
| 
 | |
| \entryheader
 | |
| \formdef{ftype-guardian}{\categorysyntax}{(ftype-guardian \var{ftype-name})}
 | |
| \returns a new ftype guardian
 | |
| \listlibraries
 | |
| \endentryheader
 | |
| 
 | |
| \var{ftype-name} must name an ftype.
 | |
| The first base field of the ftype (or one of the first base fields
 | |
| in the case of unions) must be a word-sized integer (iptr or uptr)
 | |
| with native endianness.
 | |
| This field is assumed to hold a reference count.
 | |
| 
 | |
| The return value is a new ftype guardian \var{g}, with which
 | |
| ftype-pointers of type \var{ftype-name} (or some subtype of
 | |
| \var{ftype-name}) can be registered.
 | |
| An ftype pointer is registered with \var{g} by invoking \var{g}
 | |
| with the ftype pointer as an argument.
 | |
| 
 | |
| An ftype guardian does not automatically protect from collection
 | |
| the ftype pointers registered with it,  as a normal guardian would
 | |
| do.
 | |
| Instead, for each registered ftype pointer that becomes inaccessible
 | |
| via normal (non-weak, non-guardian pointers), the guardian decrements
 | |
| the reference count of the object to which the ftype pointer points.
 | |
| If the resulting reference-count value is zero, the ftype pointer
 | |
| is preserved and can be retrieved from the guardian.
 | |
| If the resulting reference-count value is non-zero, however, the
 | |
| ftype pointer is not preserved.
 | |
| Objects retrieved from an ftype guardian (by calling it without
 | |
| arguments) are guaranteed to have zero reference counts, assuming
 | |
| reference counts are maintained properly by code outside the
 | |
| collector.
 | |
| 
 | |
| The collector decrements the reference count using the equivalent
 | |
| of \index{\scheme{ftype-locked-decr!}}\scheme{ftype-locked-decr!}
 | |
| to support systems in which non-Scheme objects are stored in memory
 | |
| shared by multiple processes.
 | |
| In such systems, programs should themselves use
 | |
| \index{\scheme{ftype-locked-incr!}}\scheme{ftype-locked-incr!} and
 | |
| \scheme{ftype-locked-decr!} or non-Scheme equivalents (e.g., the C
 | |
| \index{\scheme{LOCKED_INCR}}\scheme{LOCKED_INCR} and
 | |
| \index{\scheme{LOCKED_INCR}}\scheme{LOCKED_DECR} macros in scheme.h,
 | |
| which are described in Section~\ref{SECTFOREIGNCLIB}) to maintain
 | |
| reference counts.
 | |
| 
 | |
| The following example defines a simple ftype and an allocator for
 | |
| objects of that ftype that frees any objects of that ftype that were
 | |
| previously allocated and no longer accessible.
 | |
| 
 | |
| \schemedisplay
 | |
| (module (A make-A free-dropped-As)
 | |
|   (define-ftype A
 | |
|     (struct
 | |
|       [refcount uptr]
 | |
|       [data int]))
 | |
|   (define g (ftype-guardian A))
 | |
|   (define free-dropped-As
 | |
|     (lambda ()
 | |
|       (let ([a (g)])
 | |
|         (when a
 | |
|           (printf "freeing ~s\n" (ftype-ref A (data) a))
 | |
|           (foreign-free (ftype-pointer-address a))
 | |
|           (free-dropped-As)))))
 | |
|   (define make-A
 | |
|     (lambda (n)
 | |
|       (free-dropped-As)
 | |
|       (let ([a (make-ftype-pointer A (foreign-alloc (ftype-sizeof A)))])
 | |
|         (ftype-set! A (refcount) a 1)
 | |
|         (ftype-set! A (data) a n)
 | |
|         (g a)
 | |
|         a))))
 | |
| \endschemedisplay
 | |
| 
 | |
| We can test this by allocating, dropping, and immediately collecting
 | |
| ftype pointers to A.
 | |
| 
 | |
| \schemedisplay
 | |
| > (do ([i 10 (fx- i 1)])
 | |
|       ((fx= i 0))
 | |
|     (make-A i)
 | |
|     (collect))
 | |
| freeing 10
 | |
| freeing 9
 | |
| freeing 8
 | |
| freeing 7
 | |
| freeing 6
 | |
| freeing 5
 | |
| freeing 4
 | |
| freeing 3
 | |
| freeing 2
 | |
| > (free-dropped-As)
 | |
| freeing 1
 | |
| \endschemedisplay
 | |
| 
 | |
| Objects guarded by an ftype guardian might contain pointers to other
 | |
| objects whose reference counts should also be incremented upon
 | |
| allocation of the containing object and decremented upon freeing
 | |
| of the containing object.
 | |
| 
 | |
| 
 | |
| \section{Thread Parameters\label{SECTTHREADPARAMETERS}}
 | |
| 
 | |
| %----------------------------------------------------------------------------
 | |
| \noskipentryheader
 | |
| \formdef{make-thread-parameter}{\categoryprocedure}{(make-thread-parameter \var{object})}
 | |
| \formdef{make-thread-parameter}{\categoryprocedure}{(make-thread-parameter \var{object} \var{procedure})}
 | |
| \returns a new thread parameter
 | |
| \listlibraries
 | |
| \endnoskipentryheader
 | |
| 
 | |
| \noindent
 | |
| See Section~\ref{SECTPARAMETERS} for a general
 | |
| discussion of parameters and the use of the optional second argument.
 | |
| 
 | |
| When a thread parameter is created, a separate location is set aside
 | |
| in each current and future thread to hold the value of the parameter's
 | |
| internal state variable.
 | |
| (This location may be eliminated by the storage manager when the
 | |
| parameter becomes inaccessible.)
 | |
| Changes to the thread parameter in one thread are not seen by any
 | |
| other thread.
 | |
| 
 | |
| When a new thread is created (see \scheme{fork-thread}),
 | |
| the current value (not location) of each
 | |
| thread parameter is inherited from the forking thread by the new thread.
 | |
| Similarly, when a thread created by some other means is activated for the
 | |
| first time (see \scheme{Sactivate_thread} in
 | |
| Section~\ref{SECTFOREIGNCLIB}), the current value (not location) of each
 | |
| thread parameter is inherited from the main (original) thread by the new
 | |
| thread.
 | |
| 
 | |
| Most built-in parameters are thread parameters, but some are global.
 | |
| All are marked as global or thread where they are defined.
 | |
| There is no distinction between built-in global and thread parameters
 | |
| in the nonthreaded versions of the system.
 | |
| 
 | |
| 
 | |
| \section{Buffered I/O\label{SECTTHREADSBUFFEREDIO}}
 | |
| 
 | |
| Chez Scheme buffers file I/O operations for efficiency, but buffered
 | |
| I/O is not thread safe.
 | |
| Two threads that write to or read from the same buffered port concurrently
 | |
| can corrupt the port, resulting in buffer overruns and, ultimately,
 | |
| invalid memory references.
 | |
| 
 | |
| Buffering on binary output ports can be disabled when opened with
 | |
| buffer-mode \scheme{none}.
 | |
| Buffering on input ports cannot be completely disabled, however, due to
 | |
| the need to support lookahead, and buffering on textual ports, even
 | |
| textual output ports, cannot be disabled completely because the
 | |
| transcoders that convert between characters and bytes sometimes
 | |
| require some lookahead.
 | |
| 
 | |
| Two threads should thus \emph{never} read from or write to the same port
 | |
| concurrently, except in the special case of a binary output port
 | |
| opened buffer-mode \scheme{none}.
 | |
| Alternatives include appointing one thread to perform all I/O for a
 | |
| given port and providing a per-thread generic-port wrapper that
 | |
| forwards requests to the port only after acquiring a mutex.
 | |
| 
 | |
| The initial console and current input and output ports are thread-safe,
 | |
| as are transcript ports, so it is safe for multiple threads to print error
 | |
| and/or debugging messages to the console.
 | |
| The output may be interleaved, even within the same line, but the port
 | |
| will not become corrupted.
 | |
| Thread safety for these ports is accomplished at the high cost of
 | |
| acquiring a mutex for each I/O operation.
 | |
| 
 | |
| 
 | |
| \section{Example: Bounded Queues}
 | |
| 
 | |
| The following code, taken from the article
 | |
| ``A Scheme for native threads~\cite{Dybvig:mitchfest-threads},''
 | |
| implements a bounded queue using many of the
 | |
| thread-system features.
 | |
| A bounded queue has a fixed number of available slots.  
 | |
| Attempting to enqueue when the queue is full causes the calling thread
 | |
| to block.  
 | |
| Attempting to dequeue from an empty queue causes the calling thread
 | |
| to block.
 | |
| 
 | |
| %%% from thread article
 | |
| 
 | |
| \schemedisplay
 | |
| (define-record-type bq
 | |
|   (fields
 | |
|     (immutable data)
 | |
|     (mutable head)
 | |
|     (mutable tail)
 | |
|     (immutable mutex)
 | |
|     (immutable ready)
 | |
|     (immutable room))
 | |
|   (protocol
 | |
|     (lambda (new)
 | |
|       (lambda (bound)
 | |
|         (new (make-vector bound) 0 0 (make-mutex)
 | |
|           (make-condition) (make-condition))))))
 | |
| 
 | |
| (define dequeue!
 | |
|   (lambda (q)
 | |
|     (with-mutex (bq-mutex q)
 | |
|       (let loop ()
 | |
|         (let ([head (bq-head q)])
 | |
|           (cond
 | |
|             [(= head (bq-tail q))
 | |
|              (condition-wait (bq-ready q) (bq-mutex q))
 | |
|              (loop)]
 | |
|             [else
 | |
|              (bq-head-set! q (incr q head))
 | |
|              (condition-signal (bq-room q))
 | |
|              (vector-ref (bq-data q) head)]))))))
 | |
| 
 | |
| (define enqueue!
 | |
|   (lambda (item q)
 | |
|     (with-mutex (bq-mutex q)
 | |
|       (let loop ()
 | |
|         (let* ([tail (bq-tail q)] [tail^ (incr q tail)])
 | |
|           (cond
 | |
|             [(= tail^ (bq-head q))
 | |
|              (condition-wait (bq-room q) (bq-mutex q))
 | |
|              (loop)]
 | |
|             [else
 | |
|              (vector-set! (bq-data q) tail item)
 | |
|              (bq-tail-set! q tail^)
 | |
|              (condition-signal (bq-ready q))]))))))
 | |
| 
 | |
| (define incr
 | |
|   (lambda (q i)
 | |
|     (modulo (+ i 1) (vector-length (bq-data q)))))
 | |
| \endschemedisplay
 | |
| 
 | |
| \noindent
 | |
| The code below demonstrates the use of the bounded queue abstraction
 | |
| with a set of threads that act as consumers and producers of the
 | |
| data in the queue.
 | |
| 
 | |
| \schemedisplay
 | |
| (define job-queue)
 | |
| (define die? #f)
 | |
| 
 | |
| (define make-job
 | |
|   (let ([count 0])
 | |
|     (define fib
 | |
|       (lambda (n)
 | |
|         (if (< n 2)
 | |
|             n
 | |
|             (+ (fib (- n 2)) (fib (- n 1))))))
 | |
|     (lambda (n)
 | |
|       (set! count (+ count 1))
 | |
|       (printf "Adding job #~s = (lambda () (fib ~s))\n" count n)
 | |
|       (cons count (lambda () (fib n))))))
 | |
| 
 | |
| (define make-producer
 | |
|   (lambda (n)
 | |
|     (rec producer
 | |
|       (lambda ()
 | |
|         (printf "producer ~s posting a job\n" n)
 | |
|         (enqueue! (make-job (+ 20 (random 10))) job-queue)
 | |
|         (if die?
 | |
|             (printf "producer ~s dying\n" n)
 | |
|             (producer))))))
 | |
| 
 | |
| (define make-consumer
 | |
|   (lambda (n)
 | |
|     (rec consumer
 | |
|       (lambda ()
 | |
|         (printf "consumer ~s looking for a job~%" n)
 | |
|         (let ([job (dequeue! job-queue)])
 | |
|           (if die?
 | |
|               (printf "consumer ~s dying\n" n)
 | |
|               (begin
 | |
|                 (printf "consumer ~s executing job #~s~%" n (car job))
 | |
|                 (printf "consumer ~s computed:  ~s~%" n ((cdr job)))
 | |
|                 (consumer))))))))
 | |
| 
 | |
| (define (bq-test np nc)
 | |
|   (set! job-queue (make-bq (max nc np)))
 | |
|   (do ([np np (- np 1)])
 | |
|       ((<= np 0))
 | |
|       (fork-thread (make-producer np)))
 | |
|   (do ([nc nc (- nc 1)])
 | |
|       ((<= nc 0))
 | |
|       (fork-thread (make-consumer nc))))
 | |
| \endschemedisplay
 | |
| 
 | |
| \noindent
 | |
| Here are a possible first several lines of output from a sample run of the example program.
 | |
| 
 | |
| \schemedisplay
 | |
| > (begin
 | |
|     (bq-test 3 4)
 | |
|     (system "sleep 3")
 | |
|     (set! die? #t))
 | |
| producer 3 posting a job
 | |
| Adding job #1 = (lambda () (fib 29))
 | |
| producer 3 posting a job
 | |
| Adding job #2 = (lambda () (fib 26))
 | |
| producer 3 posting a job
 | |
| Adding job #3 = (lambda () (fib 22))
 | |
| producer 3 posting a job
 | |
| Adding job #4 = (lambda () (fib 21))
 | |
| producer 2 posting a job
 | |
| Adding job #5 = (lambda () (fib 29))
 | |
| producer 1 posting a job
 | |
| Adding job #6 = (lambda () (fib 29))
 | |
| consumer 4 looking for a job
 | |
| producer 3 posting a job
 | |
| Adding job #7 = (lambda () (fib 24))
 | |
| consumer 4 executing job #1
 | |
| consumer 3 looking for a job
 | |
| producer 2 posting a job
 | |
| Adding job #8 = (lambda () (fib 26))
 | |
| consumer 3 executing job #2
 | |
| consumer 3 computed:  121393
 | |
| consumer 3 looking for a job
 | |
| producer 1 posting a job
 | |
| Adding job #9 = (lambda () (fib 26))
 | |
| ...
 | |
| \endschemedisplay
 | |
| 
 | |
| Additional examples, including definitions of suspendable threads and
 | |
| threads that automatically terminate when they become inaccessible, are
 | |
| given in ``A Scheme for native threads~\cite{Dybvig:mitchfest-threads}.''
 | |
| 
 | |
| 
 | |
| % \section{Thread System OOP Interface}
 | |
| % 
 | |
| % The thread system OOP interface consists of one new form,
 | |
| % \scheme{define-threaded-class}.
 | |
| % This form provides a high-level interface for acquiring mutexes
 | |
| % and waiting for conditions.
 | |
| % A \scheme{define-threaded-class} form has the following general
 | |
| % syntax:
 | |
| % 
 | |
| % \schemedisplay
 | |
| % (define-threaded-class (\var{name} \var{fmls}) (\var{parent} \var{expr} \dots)
 | |
| %   (state [\var{ivar} \var{init}] \dots)
 | |
| %   (init \var{expr} \dots)
 | |
| %   (conditions
 | |
| %     [\var{cname} \var{pred}]
 | |
| %     \dots)
 | |
| %   (methods
 | |
| %     [locked \var{mname} \var{mfmls} \var{body}]
 | |
| %     \dots))
 | |
| % \endschemedisplay
 | |
| % 
 | |
| % \noindent
 | |
| % Each of the labeled sections (\scheme{state}, \scheme{init},
 | |
| % \scheme{conditions}, and \scheme{methods}) is optional, as is
 | |
| % the \scheme{locked} keyword.
 | |
| % The \scheme{locked} keyword may be applied to all, none, or
 | |
| % some of the methods in a threaded-class definition.
 | |
| % 
 | |
| % The \scheme{conditions} subform and the \scheme{locked} keyword are
 | |
| % extensions to the \scheme{define-class} syntax.
 | |
| % If no \scheme{conditions} subform is given and no \scheme{locked} keywords are
 | |
| % present, \scheme{define-threaded-class} is identical to
 | |
| % \scheme{define-class}, both in syntax and semantics.
 | |
| % 
 | |
| % If any methods are annotated with the \scheme{locked} keyword, a
 | |
| % mutex is associated with each instance of the class, and those
 | |
| % methods automatically acquire and release the mutex as if the
 | |
| % body were wrapped in a \scheme{with-mutex} form.
 | |
| % The following definition of a \scheme{stack} class demonstrates
 | |
| % the \scheme{locked} keyword.
 | |
| % 
 | |
| % \schemedisplay
 | |
| % (define-threaded-class (<stack>) (<base>)
 | |
| %   (state [pdl '()])
 | |
| %   (methods
 | |
| %     [locked push (v)
 | |
| %      (set! pdl (cons v pdl))]
 | |
| %     [locked pop (default)
 | |
| %      (if (null? pdl)
 | |
| %          default
 | |
| %          (let ([v (car pdl)])
 | |
| %            (set! pdl (cdr pdl))
 | |
| %            v))]))
 | |
| % \endschemedisplay
 | |
| % 
 | |
| % \noindent
 | |
| % The \scheme{push} method adds an item to the top of the stack.
 | |
| % The \scheme{pop} method removes an item from the top of the
 | |
| % stack and returns it, unless the stack is empty, in which case
 | |
| % it returns the default value passed in by the caller.
 | |
| % 
 | |
| % This may seem like an unnecessarily complex version of \scheme{pop}.
 | |
| % A simpler and more familiar approach would be to provide an
 | |
| % \scheme{empty?} method for determining if the contains any items
 | |
| % and to remove this test form \scheme{pop} as follows.
 | |
| % 
 | |
| % \schemedisplay
 | |
| % (define-threaded-class (<simpler-stack>) (<base>)
 | |
| %   (state [pdl '()])
 | |
| %   (methods
 | |
| %     [empty (v) (null? pdl)]
 | |
| %     [locked push (v)
 | |
| %      (set! pdl (cons v pdl))]
 | |
| %     [locked pop ()
 | |
| %      (let ([v (car pdl)])
 | |
| %        (set! pdl (cdr pdl))
 | |
| %        v)]))
 | |
| % \endschemedisplay
 | |
| % 
 | |
| % \noindent
 | |
| % Because it does not update the stack, \scheme{empty?} need not be
 | |
| % locked.
 | |
| % Unfortunately, \scheme{empty?} is not useful in a threaded environment,
 | |
| % because another thread may pop the stack in between the time
 | |
| % \scheme{empty?} and \scheme{pop} are called.
 | |
| % In general, the entire operation to be performed, including
 | |
| % any questions to be asked and any mutations to
 | |
| % be performed, must be encapsulated within a single locked
 | |
| % method.
 | |
| % 
 | |
| % It is possible to have a useful method that need not be locked.
 | |
| % The following version of \scheme{<stack>} maintains a count of
 | |
| % objects pushed onto the stack over time, and the
 | |
| % \scheme{count} method is used to retrieve this count.
 | |
| % 
 | |
| % \schemedisplay
 | |
| % (define-threaded-class (<stack>) (<base>)
 | |
| %   (state [pdl '()] [count 0])
 | |
| %   (methods
 | |
| %     [count () count]
 | |
| %     [locked push (v)
 | |
| %      (set! count (+ count 1))
 | |
| %      (set! pdl (cons v pdl))]
 | |
| %     [locked pop (default)
 | |
| %      (if (null? pdl)
 | |
| %          default
 | |
| %          (let ([v (car pdl)])
 | |
| %            (set! pdl (cdr pdl))
 | |
| %            v))]))
 | |
| % \endschemedisplay
 | |
| % 
 | |
| % \noindent
 | |
| % Although \scheme{count} may be out of date as soon as the caller
 | |
| % receives it, the value returned is guaranteed to be a valid
 | |
| % count at some time after the method call is made and before the
 | |
| % method call returns.
 | |
| % 
 | |
| % A condition variable
 | |
| % is associated with each instance of the class.
 | |
| % for each \scheme{(\var{cname} \var{pred})} pair in the
 | |
| % \scheme{conditions} subform.
 | |
| % The identifiers \scheme{\var{cname} \dots} name the associated
 | |
| % conditions within the methods of the class.
 | |
| % The predicate \var{pred} associated with a condition is an
 | |
| % expression that should evaluate to a true value if and only
 | |
| % if the condition is considered satisfied.
 | |
| % The predicate expression may refer to the instance variables
 | |
| % of the class.
 | |
| % 
 | |
| % A method waits for a condition to be satisfied using the
 | |
| % \scheme{require} form, which is valid only within the locked
 | |
| % methods of the class.
 | |
| % 
 | |
| % \schemedisplay
 | |
| % (require \var{cname} \var{body})
 | |
| % \endschemedisplay
 | |
| % 
 | |
| % \noindent
 | |
| % When a thread evaluates a \scheme{require} form, it evaluates
 | |
| % the predicate associated with \var{cname}.
 | |
| % If the predicate returns a true value, the thread proceeds to
 | |
| % evaluate \var{body}.
 | |
| % If the predicate returns false, it waits for the associated
 | |
| % condition variable, as if with an explicit call to condition wait,
 | |
| % Upon being released from the condition variable (by a signal
 | |
| % or broadcast from another thread), the thread loops back to
 | |
| % check the predicate again.
 | |
| % Control thus does not reach the body of the \scheme{require}
 | |
| % form until the predicate evaluates to a true value.
 | |
| % 
 | |
| % Waiting threads may be released from a condition by applying
 | |
| % either \scheme{condition-signal} or \scheme{condition-broadcast}
 | |
| % to the value of the corresponding \var{cname}.
 | |
| % %%% [Question: must the signaling thread be locked?]
 | |
| % 
 | |
| % Here is a \scheme{<bq>} test that redefines the bounded queue.
 | |
| % Compare this with the earlier definitions of the \scheme{<bq>},
 | |
| % \scheme{enqueue!}, and \scheme{dequeue!}.
 | |
| % 
 | |
| % \schemedisplay
 | |
| % (define-threaded-class (<bq> i) (<base>)
 | |
| %   (state [i i] [vec (make-vector i)])
 | |
| %   (conditions
 | |
| %     [ready (not (= i (vector-length vec)))]
 | |
| %     [room  (not (= i 0))])
 | |
| %   (methods
 | |
| %     [locked enqueue! (item)
 | |
| %      (require room
 | |
| %        (set! i (- i 1))
 | |
| %        (vector-set! vec i item)
 | |
| %        (condition-signal ready))]
 | |
| %     [locked dequeue! ()
 | |
| %      (require ready
 | |
| %        (let ([item (vector-ref vec i)])
 | |
| %          (set! i (+ i 1))
 | |
| %          (condition-signal room)
 | |
| %          item))]))
 | |
| % \endschemedisplay
 | |
| % 
 | |
| % 
 | 
