diff options
author | Rob Pike <r@golang.org> | 2009-10-31 18:29:06 -0700 |
---|---|---|
committer | Rob Pike <r@golang.org> | 2009-10-31 18:29:06 -0700 |
commit | 5d2a4bbc4948292501aeb7fe23f6bc5f7c6ea346 (patch) | |
tree | 2c01e8890262771a96831b119870ac1809d3008c | |
parent | 7d570bcf1c1e066bd63c9aa1d0dafdd1b71a9f08 (diff) | |
download | golang-5d2a4bbc4948292501aeb7fe23f6bc5f7c6ea346.tar.gz |
concurrency
R=go-dev, iant, rsc
http://go/go-review/1018004
-rw-r--r-- | doc/effective_go.html | 244 |
1 files changed, 240 insertions, 4 deletions
diff --git a/doc/effective_go.html b/doc/effective_go.html index 05ac22368..1bd8655fa 100644 --- a/doc/effective_go.html +++ b/doc/effective_go.html @@ -837,7 +837,7 @@ new instance each time it is evaluated. <pre> func NewFile(fd int, name string) *File { - if file < 0 { + if fd < 0 { return nil } f := File{fd, name, nil, 0}; @@ -1335,7 +1335,7 @@ In Go, enumerated constants are created using the <code>iota</code> enumerator. Since <code>iota</code> can be part of an expression and expressions can be implicitly repeated, it is easy to build intricate sets of values. -<p> +</p> <pre> type ByteSize float64 const ( @@ -1963,6 +1963,10 @@ is never used. <h3 id="sharing">Share by communicating</h3> <p> +Concurrent programming is a large topic and there is space only for some +Go-specific highlights here. +</p> +<p> Concurrent programming in many environments is made difficult by the subtleties required to implement correct access to shared variables. Go encourages a different approach in which shared values are passed around on channels @@ -1986,16 +1990,248 @@ Another way to think about this model is to consider a typical single-threaded program running on one CPU. It has no need for synchronization primitives. Now run another such instance; it too needs no synchronization. Now let those two communicate; if the communication is the synchronizer, there's still no need -for other synchronization. Consider Unix pipelines: they fit this model just -fine. Although Go's approach to concurrency originates in Hoare's +for other synchronization. Consider Unix pipelines: they fit this model +perfectly. Although Go's approach to concurrency originates in Hoare's Communicating Sequential Processes (CSP), it can also be seen as a type-safe generalization of Unix pipes. </p> <h3 id="goroutines">Goroutines</h3> +<p> +They're called <em>goroutines</em> because the existing +terms—threads, coroutines, processes, and so on—convey +inaccurate connotations. A goroutine has a simple model: it is a +function executing in parallel with other goroutines in the same +address space. It is lightweight, costing little more than the +allocation of stack space. +And the stacks start small, so they are cheap, and grow +by allocating (and freeing) heap storage as required. +</p> +<p> +Goroutines are multiplexed onto multiple OS threads so if one should +block, such as while waiting for I/O, others continue to run. Their +design hides many of the complexities of thread creation and +management. +</p> +<p> +Prefix a function or method call with the <code>go</code> +keyword to run the call in a new goroutine. +When the call completes, the goroutine +exits, silently. (The effect is similar to the Unix shell's +<code>&</code> notation for running a command in the +background.) +</p> +<pre> +go list.Sort(); // run list.Sort in parallel; don't wait for it. +</pre> +<p> +A function literal can be handy in a goroutine invocation. +<pre> +func Announce(message string, delay int64) { + go func() { + time.Sleep(delay); + fmt.Println(message); + }() // Note the parentheses - must call the function. +} +</pre> +<p> +In Go function literals are closures: the implementation makes +sure the variables referred to by the function survive as long as they are active. +<p> +These examples aren't too practical because the functions have no way of signaling +completion. For that, we need channels. +</p> + <h3 id="channels">Channels</h3> +<p> +Like maps, channels are a reference type and are allocated with <code>make</code>. +If an optional integer parameter is provided, it sets the buffer size for the channel. +The default is zero, for an unbuffered or synchronous channel. +</p> +<pre> +ci := make(chan int); // unbuffered channel of integers +cj := make(chan int, 0); // unbuffered channel of integers +cs := make(chan *os.File, 100); // buffered channel of pointers to Files +</pre> +<p> +Channels combine communication—the exchange of a value—with +synchronization—guaranteeing that two calculations (goroutines) are in +a known state. +</p> +<p> +There are lots of nice idioms using channels. Here's one to get us started. +In the previous section we launched a sort in the background. A channel +can allow the launching goroutine to wait for the sort to complete. +</p> +<pre> +c := make(chan int); // Allocate a channel. +// Start the sort in a goroutine; when it completes, signal on the channel. +go func() { + list.Sort(); + c <- 1; // Send a signal; value does not matter. +}(); +doSomethingForAWhile(); +<-c; // Wait for sort to finish; discard sent value. +</pre> +<p> +Receivers always block until there is data to receive. +If the channel is unbuffered, the sender blocks until the receiver has +received the value. +If the channel has a buffer, the sender blocks only until the +value has been copied to the buffer. +</p> +<p> +A buffered channel can be used like a semaphore, for instance to +limit throughput. In this example, incoming requests are passed +to <code>handle</code>, which sends a value into the channel, processes +the request, and then receives a value out of the channel. +The capacity of the channel buffer limits the number of +simultaneous calls to <code>process</code>. +</p> +<pre> +var sem = make(chan int, MaxOutstanding) + +func handle(r *Request) { + sem <- 1; // Wait for active queue to drain. + process(r); // May take a long time. + <-sem; // Done; enable next request to run. +} + +func Serve(queue chan *Request) { + for { + req := <-queue; + go handle(req); // Don't wait for handle to finish. + } +} +</pre> +<p> +Here's the same idea implemented by starting a fixed +number of <code>handle</code> goroutines all reading from the request +channel. +The number of goroutines limits the number of simultaneous +calls to <code>process</code>. +This <code>Serve</code> function also accepts a channel on which +it will be told to exit; after launching the goroutines it blocks +receiving from that channel. +</p> +<pre> +func handle(queue chan *Request) { + for r := range queue { + process(r); + } +} + +func Serve(clientRequests chan *clientRequests, quit chan bool) { + // Start handlers + for i := 0; i < MaxOutstanding; i++ { + go handle(clientRequests) + } + <-quit; // Wait to be told to exit. +} +</pre> + +<h3 id="chan_of_chan">Channels of channels</h3> +<p> +One of the most important properties of Go is that +a channel is a first-class value that can be allocated and passed +around like any other. A common use of this property is +to implement safe, parallel demultiplexing. +<p> +In the example in the previous section, <code>handle</code> was +an idealized handler for a request but we didn't define the +type it was handling. If that type includes a channel on which +to reply, each client can provide its own path for the answer. +Here's a schematic definition of type <code>Request</code>. +</p> +<pre> +type Request struct { + args []int; + f func([]int) int; + resultChan <-chan int; +} +</pre> +<p> +The client provides a function and its arguments, as well as +a channel inside the request object on which to receive the answer. +</p> +<pre> +func sum(a []int) (s int) { + for _, v := range a { + s += v + } + return +} + +request := &Request{[]int{3, 4, 5}, sum, make(chan int)} +// Send request +client Requests <- request; +// Wait for response. +fmt.Printf("answer: %d\n", <-request.resultChan); +</pre> +<p> +On the server side, the handler function is the only thing that changes. +</p> +<pre> +func handle(queue chan *Request) { + for req := range queue { + req.resultChan <- req.f(req.args); + } +} +</pre> +<p> +There's clearly a lot more to do to make it realistic, but this +code is a framework for a rate-limited, parallel, non-blocking RPC +system, and there's not a mutex in sight. +</p> + +<h3 id="parallel">Parallelization</h3> +<p> +Another application of these ideas is to parallelize a calculation +across multiple CPU cores. If the calculation can be broken into +separate pieces, it can be parallelized, with a channel to signal +when each piece completes. +</p> +<p> +Let's say we have an expensive operation to perform on an array of items, +and that the value of the operation on each item is independent, +as in this idealized example. +</p> +<pre> +type Vec []float64 + +// Apply the operation to n elements of v starting at i. +func (v Vec) DoSome(i, n int, u Vec, c chan int) { + for ; i < n; i++ { + v[i] += u.Op(v[i]) + } + c <- 1; // signal that this piece is done +} +</pre> +<p> +We launch the pieces independently in a loop, one per CPU. +They can complete in any order but it doesn't matter; we just +count the completion signals by draining the channel after +launching all the goroutines. +</p> +<pre> +const NCPU = 4 // number of CPU cores + +func (v Vec) DoAll(u Vec) { + c := make(chan int, NCPU); // Buffering optional but sensible. + for i := 0; i < NCPU; i++ { + go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c); + } + // Drain the channel. + for i := 0; i < NCPU; i++ { + <-c // wait for one task to complete + } + // All done. +} + +</pre> + <h3 id="leaky_buffer">A leaky buffer</h3> <p> |