guide: small edits to concurrency chapter

Also, split sections on futures and places into a "parallelism" chapter.
This commit is contained in:
Matthew Flatt 2013-11-02 12:32:02 -06:00
parent c692f13200
commit a5c655c716
7 changed files with 129 additions and 57 deletions

View File

@ -1,16 +1,29 @@
#lang scribble/doc #lang scribble/doc
@(require scribble/manual scribble/eval "guide-utils.rkt") @(require scribble/manual
scribble/eval
"guide-utils.rkt"
(for-label racket))
@(define concurrency-eval (make-base-eval)) @(define concurrency-eval (make-base-eval))
@(define reference-doc '(lib "scribblings/reference/reference.scrbl")) @(define reference-doc '(lib "scribblings/reference/reference.scrbl"))
@title[#:tag "concurrency"]{Concurrency, Threads, and Synchronization} @title[#:tag "concurrency"]{Concurrency and Synchronization}
@section{Thread basics} Racket provides @deftech{concurrency} in the form of
@deftech{threads}, and it provides a general @racket[sync] function
that can be used to synchronize both threads and other implicit forms of
concurrency, such as @tech{ports}.
To execute a procedure concurrrently, use @racket[thread]. This example Threads run concurrently in the sense that one thread can preempt
creates 2 new threads from the main thread: another without its cooperation, but threads do not run in parallel in
the sense of using multiple hardware processors. See
@secref["parallelism"] for information on parallelism in Racket.
@section{Threads}
To execute a procedure concurrrently, use @racket[thread]. The
following example creates two new threads from the main thread:
@racketblock[ @racketblock[
(displayln "This is the original thread") (displayln "This is the original thread")
@ -36,8 +49,8 @@ uses @racket[kill-thread] to terminate the worker thread:
clicked, so in DrRacket the @racket[thread-wait] is not necessary.} clicked, so in DrRacket the @racket[thread-wait] is not necessary.}
If the main thread finishes or is killed, the application exits, even if If the main thread finishes or is killed, the application exits, even if
other threads were still running. A thread can use @racket[thread-wait] to other threads are still running. A thread can use @racket[thread-wait] to
wait for another thread to finish. Here the main thread uses wait for another thread to finish. Here, the main thread uses
@racket[thread-wait] to make sure the worker thread finishes before the main @racket[thread-wait] to make sure the worker thread finishes before the main
thread exits: thread exits:
@ -52,9 +65,9 @@ thread exits:
@section{Thread Mailboxes} @section{Thread Mailboxes}
Each thread has a mailbox for receiving messages. @racket[thread-send] Each thread has a mailbox for receiving messages. The @racket[thread-send] function
asynchronously sends a message to another thread's mailbox, while asynchronously sends a message to another thread's mailbox, while
@racket[thread-receive] will return the oldest message from the current @racket[thread-receive] returns the oldest message from the current
thread's mailbox, blocking to wait for a message if necessary. In the thread's mailbox, blocking to wait for a message if necessary. In the
following example, the main thread sends data to the worker thread to be following example, the main thread sends data to the worker thread to be
processed, then sends a @racket['done] message when there is no more data and processed, then sends a @racket['done] message when there is no more data and
@ -76,7 +89,7 @@ waits for the worker thread to finish.
(thread-wait worker-thread) (thread-wait worker-thread)
] ]
In the next example the main thread delegates work to multiple arithmetic In the next example, the main thread delegates work to multiple arithmetic
threads, then waits to receive the results. The arithmetic threads process work threads, then waits to receive the results. The arithmetic threads process work
items then send the results to the main thread. items then send the results to the main thread.
@ -87,7 +100,10 @@ items then send the results to the main thread.
(match (thread-receive) (match (thread-receive)
[(list oper1 oper2 result-thread) [(list oper1 oper2 result-thread)
(thread-send result-thread (thread-send result-thread
(format "~a + ~a = ~a" oper1 oper2 (operation oper1 oper2))) (format "~a + ~a = ~a"
oper1
oper2
(operation oper1 oper2)))
(loop)]))))) (loop)])))))
(define addition-thread (make-arithmetic-thread +)) (define addition-thread (make-arithmetic-thread +))
@ -97,9 +113,11 @@ items then send the results to the main thread.
(for ([item worklist]) (for ([item worklist])
(match item (match item
[(list '+ o1 o2) [(list '+ o1 o2)
(thread-send addition-thread (list o1 o2 (current-thread)))] (thread-send addition-thread
(list o1 o2 (current-thread)))]
[(list '- o1 o2) [(list '- o1 o2)
(thread-send subtraction-thread (list o1 o2 (current-thread)))])) (thread-send subtraction-thread
(list o1 o2 (current-thread)))]))
(for ([i (length worklist)]) (for ([i (length worklist)])
(displayln (thread-receive))) (displayln (thread-receive)))
@ -114,10 +132,10 @@ single resource.
In the following example, multiple threads print to standard output In the following example, multiple threads print to standard output
concurrently. Without synchronization, a line printed by one thread might concurrently. Without synchronization, a line printed by one thread might
appear in the middle of a line printed by another thread. By using a semaphore appear in the middle of a line printed by another thread. By using a semaphore
initialized with a count of 1, only 1 thread will print at a time. initialized with a count of @racket[1], only one thread will print at a time.
@racket[semaphore-wait] blocks until the semaphore's internal counter is The @racket[semaphore-wait] function blocks until the semaphore's internal counter is
non-zero, then decrements the counter and returns. @racket[semaphore-post] non-zero, then decrements the counter and returns. The @racket[semaphore-post] function
increments the counter so that another thread can unblock then print. increments the counter so that another thread can unblock and then print.
@racketblock[ @racketblock[
(define output-semaphore (make-semaphore 1)) (define output-semaphore (make-semaphore 1))
@ -132,8 +150,27 @@ increments the counter so that another thread can unblock then print.
(for-each thread-wait threads) (for-each thread-wait threads)
] ]
Semaphores are a low-level technique. Often a better solution is to restrict The pattern of waiting on a semaphore, working, and posting to the
resource access to a single thread. For instance, synchronizing access to semaphore can also be expressed using
@racket[call-with-semaphore],which has the advantage of posting to the
semaphore if control escapes (e.g., due to an exception):
@racketblock[
(define output-semaphore (make-semaphore 1))
(define (make-thread name)
(thread (lambda ()
(for [(i 10)]
(call-with-semaphore
output-semaphore
(lambda ()
(printf "thread ~a: ~a~n" name i)))))))
(define threads
(map make-thread '(A B C)))
(for-each thread-wait threads)
]
Semaphores are a low-level technique. Often, a better solution is to restrict
resource access to a single thread. For example, synchronizing access to
standard output might be better accomplished by having a dedicated thread for standard output might be better accomplished by having a dedicated thread for
printing output. printing output.
@ -145,10 +182,10 @@ channel, so channels should be used when multiple threads need to consume items
from a single work queue. from a single work queue.
In the following example, the main thread adds items to a channel using In the following example, the main thread adds items to a channel using
@racket[channel-put] while multiple worker threads consume those items using @racket[channel-put], while multiple worker threads consume those items using
@racket[channel-get]. Each call to either procedure may block until another @racket[channel-get]. Each call to either procedure blocks until another
thread calls the other procedure with the same channel. The workers process thread calls the other procedure with the same channel. The workers process
the items and then pass their results to the result-thread via the result-channel. the items and then pass their results to the result thread via the @racket[result-channel].
@racketblock[ @racketblock[
(define result-channel (make-channel)) (define result-channel (make-channel))
@ -183,17 +220,20 @@ the items and then pass their results to the result-thread via the result-channe
@section{Buffered Asynchronous Channels} @section{Buffered Asynchronous Channels}
Buffered asynchronous channels are similar to the channels described above, but Buffered asynchronous channels are similar to the channels described above, but
the put operation of asynchronous channels does not block unless the given the ``put'' operation of asynchronous channels does not block---unless the given
channel was created with a buffer limit and the limit has been reached. The channel was created with a buffer limit and the limit has been reached. The
asynchronous put operation is therefore somewhat similar to asynchronous-put operation is therefore somewhat similar to
@racket[thread-send], but unlike thread mailboxes, asynchronous channels allow @racket[thread-send], but unlike thread mailboxes, asynchronous channels allow
multiple threads to consume items from a single channel. In the following multiple threads to consume items from a single channel.
In the following
example, the main thread adds items to the work channel, which holds a maximum example, the main thread adds items to the work channel, which holds a maximum
of 3 items at a time. The worker threads process items from this channel and of three items at a time. The worker threads process items from this channel and
then send results to the print thread. then send results to the print thread.
@racketblock[ @racketblock[
(require racket/async-channel) (require racket/async-channel)
(define print-thread (define print-thread
(thread (lambda () (thread (lambda ()
(let loop () (let loop ()
@ -202,6 +242,7 @@ then send results to the print thread.
(define (safer-printf . items) (define (safer-printf . items)
(thread-send print-thread (thread-send print-thread
(apply format items))) (apply format items)))
(define work-channel (make-async-channel 3)) (define work-channel (make-async-channel 3))
(define (make-worker-thread thread-id) (define (make-worker-thread thread-id)
(thread (thread
@ -210,6 +251,7 @@ then send results to the print thread.
(define item (async-channel-get work-channel)) (define item (async-channel-get work-channel))
(safer-printf "Thread ~a processing item: ~a" thread-id item) (safer-printf "Thread ~a processing item: ~a" thread-id item)
(loop))))) (loop)))))
(for-each make-worker-thread '(1 2 3)) (for-each make-worker-thread '(1 2 3))
(for ([item '(a b c d e f g h i j k l m)]) (for ([item '(a b c d e f g h i j k l m)])
(async-channel-put work-channel item)) (async-channel-put work-channel item))
@ -220,16 +262,17 @@ processed. If the main thread were to exit without such synchronization, it is
possible that the worker threads will not finish processing some items or the possible that the worker threads will not finish processing some items or the
print thread will not print all items. print thread will not print all items.
@section{Synchronizable events and sync} @section{Synchronizable Events and @racket[sync]}
There are other ways to synchronize threads. The @racket[sync] function allows There are other ways to synchronize threads. The @racket[sync] function allows
threads to coordinate via @tech[#:doc reference-doc]{synchronizable events}s. threads to coordinate via @tech[#:doc reference-doc]{synchronizable events}.
Many types double as events, allowing a uniform way to synchronize threads Many values double as events, allowing a uniform way to synchronize threads
using different types. Examples include channels, ports, and alarms. using different types. Examples of events include channels, ports, threads,
and alarms.
In the next example, a channel and an alarm are used as synchronizable events. In the next example, a channel and an alarm are used as synchronizable events.
The workers sync on both in order to process the channel items up until the The workers @racket[sync] on both so that they can process channel items until the
alarm is activated. The channel items are processed and then results sent back alarm is activated. The channel items are processed, and then results are sent back
to the main thread. to the main thread.
@racketblock[ @racketblock[
@ -264,17 +307,19 @@ to the main thread.
The next example shows a function for use in a simple TCP echo server. The The next example shows a function for use in a simple TCP echo server. The
function uses @racket[sync/timeout] to synchronize on input from the given port function uses @racket[sync/timeout] to synchronize on input from the given port
or a message in the thread's mailbox. @racket[sync/timeout]'s first argument or a message in the thread's mailbox. The first argument to @racket[sync/timeout]
specifies the maximum number of seconds it should wait on the given events. specifies the maximum number of seconds it should wait on the given events. The
@racket[read-line-evt] returns an event that is ready when a line of input is @racket[read-line-evt] function returns an event that is ready when a line of input is
available in the given input port. @racket[thread-receive-evt] is ready when available in the given input port. The result of @racket[thread-receive-evt] is ready when
@racket[thread-receive] would not block. In a real application, the messages @racket[thread-receive] would not block. In a real application, the messages
received in the thread mailbox could be used for control messages, etc. received in the thread mailbox could be used for control messages, etc.
@racketblock[ @racketblock[
(define (serve in-port out-port) (define (serve in-port out-port)
(let loop [] (let loop []
(define evt (sync/timeout 2 (read-line-evt in-port 'any) (thread-receive-evt))) (define evt (sync/timeout 2
(read-line-evt in-port 'any)
(thread-receive-evt)))
(cond (cond
[(not evt) [(not evt)
(displayln "Timed out, exiting") (displayln "Timed out, exiting")
@ -285,18 +330,19 @@ received in the thread mailbox could be used for control messages, etc.
(flush-output out-port) (flush-output out-port)
(loop)] (loop)]
[else [else
(printf "Received a message in mailbox: ~a~n" (thread-receive)) (printf "Received a message in mailbox: ~a~n"
(thread-receive))
(loop)]))) (loop)])))
] ]
The previous example could be used in a program like the following. This code The @racket[serve] function is used in the following example, which
starts a server thread and a client thread which communicate over TCP. The starts a server thread and a client thread that communicate over TCP. The
client prints 3 lines to the server, which echoes them back. The client's client prints three lines to the server, which echoes them back. The client's
copy-port call will block until EOF is received. The server will timeout after @racket[copy-port] call blocks until EOF is received. The server times out after
2 seconds, closing the ports which then allows copy-port to finish and the two seconds, closing the ports, which allows @racket[copy-port] to finish and the
client will exit. The main thread uses @racket[thread-wait] to wait for the client to exit. The main thread uses @racket[thread-wait] to wait for the
client-thread to exit. Without @racket[thread-wait], the main thread might client thread to exit (since, without @racket[thread-wait], the main thread might
exit before the other threads are finished. exit before the other threads are finished).
@racketblock[ @racketblock[
(define port-num 4321) (define port-num 4321)
@ -321,13 +367,13 @@ exit before the other threads are finished.
(thread-wait client-thread) (thread-wait client-thread)
] ]
Sometimes you want to attach result behavior directly to the event passed to Sometimes, you want to attach result behavior directly to the event passed to
@racket[sync]. In the following example, the worker thread synchronizes on 3 @racket[sync]. In the following example, the worker thread synchronizes on three
channels, but each channel must be handled differently. Using channels, but each channel must be handled differently. Using
@racket[handle-evt] associates a callback with the given event. When @racket[handle-evt] associates a callback with the given event. When
@racket[sync] selects the given event, it calls the callback to generate the @racket[sync] selects the given event, it calls the callback to generate the
synchronization result, rather than using the event's normal synchronization synchronization result, rather than using the event's normal synchronization
result. Because the event is handled in the callback, there is no need to result. Since the event is handled in the callback, there is no need to
dispatch on the return value of @racket[sync]. dispatch on the return value of @racket[sync].
@racketblock[ @racketblock[
@ -362,8 +408,9 @@ dispatch on the return value of @racket[sync].
(channel-put append-channel '("a" "b")) (channel-put append-channel '("a" "b"))
] ]
@racket[handle-evt] invokes the callback in tail position, so it is safer to The result of @racket[handle-evt] invokes its callback in tail position
use recursion, as in the following example. with respect to @racket[sync], so it is safe to
use recursion as in the following example.
@racketblock[ @racketblock[
(define control-channel (make-channel)) (define control-channel (make-channel))
@ -393,3 +440,8 @@ use recursion, as in the following example.
(thread-wait worker) (thread-wait worker)
] ]
The @racket[wrap-evt] function is like @racket[handle-evt], except
that its handler is not called in tail position with respect to
@racket[sync]. At the same time, @racket[wrap-evt] disables break
exceptions during its handler's invocation.

View File

@ -10,7 +10,7 @@
@title[#:tag "effective-futures"]{Parallelism with Futures} @title[#:tag "effective-futures"]{Parallelism with Futures}
The @racketmodname[racket/future] library provides support for The @racketmodname[racket/future] library provides support for
performance improvement through parallelism with the @racket[future] performance improvement through parallelism with @deftech{futures} and the @racket[future]
and @racket[touch] functions. The level of parallelism available from and @racket[touch] functions. The level of parallelism available from
those constructs, however, is limited by several factors, and the those constructs, however, is limited by several factors, and the
current implementation is best suited to numerical tasks. current implementation is best suited to numerical tasks.

View File

@ -55,6 +55,8 @@ precise details to @|Racket| and other reference manuals.
@include-section["performance.scrbl"] @include-section["performance.scrbl"]
@include-section["parallelism.scrbl"]
@include-section["running.scrbl"] @include-section["running.scrbl"]
@include-section["other.scrbl"] @include-section["other.scrbl"]

View File

@ -0,0 +1,20 @@
#lang scribble/doc
@(require scribble/manual "guide-utils.rkt"
(for-label racket/flonum
racket/unsafe/ops
racket/performance-hint))
@title[#:tag "parallelism"]{Parallelism}
Racket provides two forms of @deftech{parallelism}: @tech{futures} and
@tech{places}. On a platform that provides multiple processors,
parallelism can improve the run-time performance of a program.
See also @secref["performance"] for information on sequential
performance in Racket. Racket also provides threads for
@tech{concurrency}, but threads do not provide parallelism; see
@secref["concurrency"] for more information.
@include-section["futures.scrbl"]
@include-section["places.scrbl"]
@include-section["distributed.scrbl"]

View File

@ -520,8 +520,3 @@ occurrence of the variable @racket[_fishes]. That constitutes
a reference to the list, ensuring that the list is not itself a reference to the list, ensuring that the list is not itself
garbage collected, and thus the red fish is not either. garbage collected, and thus the red fish is not either.
@; ----------------------------------------------------------------------
@include-section["futures.scrbl"]
@include-section["places.scrbl"]
@include-section["distributed.scrbl"]

View File

@ -3,6 +3,8 @@
@title[#:tag "threads"]{Threads} @title[#:tag "threads"]{Threads}
@guideintro["concurrency"]{threads}
See @secref["thread-model"] for basic information on the Racket See @secref["thread-model"] for basic information on the Racket
thread model. See also @secref["futures"]. thread model. See also @secref["futures"].

View File

@ -53,6 +53,7 @@
"Mike T. McHenry, " "Mike T. McHenry, "
"Philippe Meunier, " "Philippe Meunier, "
"Scott Owens, " "Scott Owens, "
"David T. Pierson, "
"Jon Rafkind, " "Jon Rafkind, "
"Jamie Raymond, " "Jamie Raymond, "
"Grant Rettke, " "Grant Rettke, "