1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967
|
% 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
endiannes 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
%
%
|