1
0
mirror of https://github.com/golang/go synced 2024-11-22 06:44:40 -07:00

replace 235 with sieve - less problematic

add programs, not yet described, to demonstrate servers.

R=gri
DELTA=279  (177 added, 16 deleted, 86 changed)
OCL=15380
CL=15389
This commit is contained in:
Rob Pike 2008-09-16 11:00:11 -07:00
parent 721cb9c6a1
commit db9002f152
6 changed files with 256 additions and 79 deletions

View File

@ -212,9 +212,9 @@ An Interlude about Constants
Although integers come in lots of sizes in Go, integer constants do not. Although integers come in lots of sizes in Go, integer constants do not.
There are no constants like "0ll" or "0x0UL". Instead, integer There are no constants like "0ll" or "0x0UL". Instead, integer
constants are evaluated as ideal, arbitrary precision values that constants are evaluated as ideal, large-precision values that
can overflow only when they are assigned to an integer variable of can overflow only when they are assigned to an integer variable with
some specific size. too little precision to represent the value.
const hard_eight = (1 << 100) >> 97 // legal const hard_eight = (1 << 100) >> 97 // legal
@ -313,7 +313,8 @@ initialization statement. The "switch" on line 12 uses one to create variables
"nr" and "er" to hold the return values from "fd.Read()". (The "if" on line 19 "nr" and "er" to hold the return values from "fd.Read()". (The "if" on line 19
has the same idea.) The "switch" statement is general: it evaluates the cases has the same idea.) The "switch" statement is general: it evaluates the cases
from top to bottom looking for the first case that matches the value; the from top to bottom looking for the first case that matches the value; the
case expressions don't need to be constants or even integers. case expressions don't need to be constants or even integers, as long as
they all have the same type.
Since the "switch" value is just "true", we could leave it off -- as is also true Since the "switch" value is just "true", we could leave it off -- as is also true
in a "for" statement, a missing value means "true". In fact, such a "switch" in a "for" statement, a missing value means "true". In fact, such a "switch"
@ -417,59 +418,59 @@ to implement the three methods for that type, like this:
--PROG progs/sortmain.go /type.Day/ /swap/ --PROG progs/sortmain.go /type.Day/ /swap/
The 2,3,5 program Prime numbers
---- ----
Now we come to processes and communication - concurrent programming. Now we come to processes and communication -- concurrent programming.
It's a big subject so to be brief we assume some familiarity with the topic. It's a big subject so to be brief we assume some familiarity with the topic.
The prime sieve program in the language specification document is A classic program in the style is the prime sieve of Eratosthenes.
an excellent illustration of concurrent programming, but for variety It works by taking a stream of all the natural numbers, and introducing
here we'll solve a different problem in a similar way. a sequence of filters, one for each prime, to winnow the multiples of
that prime. At each step we have a sequence of filters of the primes
so far, and the next number to pop out is the next prime, which triggers
the creation of the next filter in the chain.
An old interview question is to write a program that prints all the Here's a flow diagram; each box represents a filter element whose
integers that can be written as multiples of 2, 3, and 5 only. creation is triggered by the first number that flowed from the
One way to solve it is to generate streams of numbers multiplied elements before it.
by 2, 3, and 5, and to provide as input to the stream generators
the output of the program so far. To generate the correct output,
we pick the least number generated each round and eliminate
duplicates (6 appears twice, as 2*3s and as 3*2), but that's easy.
Here's a flow diagram:
<br> <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src=go235.jpg > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src='sieve.gif'>
<br> <br>
To create a stream of integers, we use a Go <i>channel</i>, which, To create a stream of integers, we use a Go <i>channel</i>, which,
borrowing from CSP and its descendants, represents a communications borrowing from CSP's descendants, represents a communications
channel that can connect two computations. In Go, channel variables are channel that can connect two concurrent computations.
always pointers to channels -- it's the (hidden) object they point to that In Go, channel variables are
always pointers to channels -- it's the object they point to that
does the communication. does the communication.
Here are the first few lines of "progs/235A.go": Here is the first function in "progs/sieve.go":
--PROG progs/235A.go /package/ /^}/ --PROG progs/sieve.go /Send/ /^}/
The numbers can get big, so we'll use 64-bit unsigned integers, The function "Generate" sends the sequence 2, 3, 4, 5, ... to its
using the shorthand "INT" defined on line 3. argument channel, "ch", using the binary send operator "-&lt".
Channels block, so if there's no recipient for the the value on "ch",
the send operation will wait until one becomes available.
The function M is a multiplication generator. It receives data The "Filter" function has three arguments: an input channel, an output
on the channel "in", using the unary receive operator "&lt;-"; the expression channel, and a prime number. It copies values from the input to the
"&lt;-in" retrieves the next value on the the channel. The value output, discarding anything divisible by the prime. The unary prefix
is multiplied by the factor "f" and then sent out on channel "out", operator "&lt;-" (receive) retrieves the next value on the channel.
using the binary send operator "-&lt". Channels block, so if there's
nothing available on "in" or no recipient for the the value on "out",
the function will block until it can proceed.
To deal with blocking, we want M to run in a separate thread. Go has --PROG progs/sieve.go /Copy/ /^}/
The generator and filters execute concurrently. Go has
its own model of process/threads/light-weight processes/coroutines, its own model of process/threads/light-weight processes/coroutines,
so to avoid notational confusion we'll call concurrently executing so to avoid notational confusion we'll call concurrently executing
computations in Go <i>goroutines</i>. To start a goroutine, computations in Go <i>goroutines</i>. To start a goroutine,
invoke the function, prefixing the call with the keyword "go"; invoke the function, prefixing the call with the keyword "go";
this starts the function running independently of the current this starts the function running in parallel with the current
computation but in the same address space: computation but in the same address space:
go sum(huge_array); // calculate sum in the background go sum(huge_array); // calculate sum in the background
@ -482,60 +483,48 @@ on which it can report back:
// ... do something else for a while // ... do something else for a while
result := <-ch; // wait for, and retrieve, result result := <-ch; // wait for, and retrieve, result
Back to our 2-3-5 program. Here's how "main" sets up the Back to our prime sieve. Here's how the sieve pipeline is stitched
calculation: together:
--PROG progs/235A.go /func.main/ /go.M.5/ --PROG progs/sieve.go /func.main/ /^}/
Lines 17 through 22 create the channels to connect the multipliers, Line 23 creates the initial channel to pass to "Generate", which it
and lines 24 through 26 launch the goroutines. The "100" parameter then starts up. As each prime pops out of the channel, a new "Filter"
to the input channels ("c2i" etc.) is a buffer size. By default, is added to the pipeline and <i>its</i> output becomes the new value
Go channels are unbuffered (synchronous) but the "Multipler" inputs need to of "ch".
be buffered because the main loop will generate data faster than
they process it.
Next we initialize a few variables. The sieve program can be tweaked to use a pattern common
in this style of programming. Here is a variant version
of "Generate", from "progs/sieve1.go":
--PROG progs/sieve1.go /func.Generate/ /^}/
--PROG progs/235A.go /x.:=/ /x5/ This version does all the setup internally. It creates the output
channel, launches a goroutine internally using a function literal, and
returns the channel to the caller. It is a factory for concurrent
execution, starting the goroutine and returning its connection.
The same
change can be made to "Filter":
The "x" variable will be the value we generate; the others will --PROG progs/sieve1.go /func.Filter/ /^}/
hold the latest value received from each "Multiplier" goroutine.
Finally, here is the main loop: The "Sieve" function's main loop becomes simpler and clearer as a
result, and while we're at it let's turn it into a factory too:
--PROG progs/235A.go /for.*100/ /^.}/ --PROG progs/sieve1.go /func.Sieve/ /^}/
The algorithm is simple: We send the current value to each of Now "main"'s interface to the prime sieve is a channel of primes:
the "Multiplier" goroutines; it needs to be multiplied by 2, 3, and 5 to
produce the full list. Next, we advance the streams: each
channel whose latest value is the current value needs to step
to the next value. Finally, we choose the least of the current
values, and iterate.
This program can be tightened up a little using a pattern common --PROG progs/sieve1.go /func.main/ /^}/
in this style of programming. Here is a variant version of "Multiplier",
from "progs/235B.go":
--PROG progs/235B.go /func.M/ /^}/ Service
----
This version does all the setup internally. It creates the channels, here we will describe this server:
launches a goroutine internally using a function literal, and
returns the channels to the caller. It is a concurrent factory,
starting the goroutine and returning its connections.
The "main" function starts out simpler as a result: --PROG progs/server.go
--PROG progs/235B.go /func.main/ /x5/ and this modification, which exits cleanly
The rest is the same. --PROG progs/server1.go /func.Server/ END
The program "progs/235_gen.go" generalizes the problem; by
filling in the elements of an array "F"
--PROG progs/235_gen.go /F.*INT/
we can produces outputs from multiples of any integers.
Here is the full program, without further elucidation.
--PROG progs/235_gen.go

View File

@ -11,15 +11,14 @@ import (
var rot13_flag = Flag.Bool("rot13", false, nil, "rot13 the input") var rot13_flag = Flag.Bool("rot13", false, nil, "rot13 the input")
func rot13(bb byte) byte { func rot13(b byte) byte {
var b int = int(bb) /// BUG: until byte division is fixed
if 'a' <= b && b <= 'z' { if 'a' <= b && b <= 'z' {
b = 'a' + ((b - 'a') + 13) % 26; b = 'a' + ((b - 'a') + 13) % 26;
} }
if 'A' <= b && b <= 'Z' { if 'A' <= b && b <= 'Z' {
b = 'A' + ((b - 'A') + 13) % 26 b = 'A' + ((b - 'A') + 13) % 26
} }
return byte(b) return b
} }
type Reader interface { type Reader interface {

49
doc/progs/server.go Normal file
View File

@ -0,0 +1,49 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
type BinOp (a, b int) int;
type Request struct {
a, b int;
replyc *chan int;
}
func Run(op *BinOp, request *Request) {
result := op(request.a, request.b);
request.replyc -< result;
}
func Server(op *BinOp, service *chan *Request) {
for {
request := <-service;
go Run(op, request); // don't wait for it
}
}
func StartServer(op *BinOp) *chan *Request {
req := new(chan *Request);
go Server(op, req);
return req;
}
func main() {
adder := StartServer(func(a, b int) int { return a + b });
const N = 100;
var reqs [N]Request;
for i := 0; i < N; i++ {
req := &reqs[i];
req.a = i;
req.b = i + N;
req.replyc = new(chan int);
adder -< req;
}
for i := N-1; i >= 0; i-- { // doesn't matter what order
if <-reqs[i].replyc != N + 2*i {
print("fail at ", i, "\n");
}
}
print("done\n");
}

55
doc/progs/server1.go Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
type BinOp (a, b int) int;
type Request struct {
a, b int;
replyc *chan int;
}
func Run(op *BinOp, request *Request) {
result := op(request.a, request.b);
request.replyc -< result;
}
func Server(op *BinOp, service *chan *Request, quit *chan bool) {
for {
var request *Request;
select {
case request <- service: // can't say request := <-service here yet
go Run(op, request); // don't wait for it
case <-quit:
return;
}
}
}
func StartServer(op *BinOp) (servch *chan *Request, quitch *chan bool) {
service := new(chan *Request);
quit := new(chan bool);
go Server(op, service, quit);
return service, quit;
}
func main() {
adder, quit := StartServer(func(a, b int) int { return a + b });
const N = 100;
var reqs [N]Request;
for i := 0; i < N; i++ {
req := &reqs[i];
req.a = i;
req.b = i + N;
req.replyc = new(chan int);
adder -< req;
}
for i := N-1; i >= 0; i-- { // doesn't matter what order
if <-reqs[i].replyc != N + 2*i {
print("fail at ", i, "\n");
}
}
quit -< true;
}

36
doc/progs/sieve.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
// Send the sequence 2, 3, 4, ... to channel 'ch'.
func Generate(ch *chan int) {
for i := 2; ; i++ {
ch -< i // Send 'i' to channel 'ch'.
}
}
// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in *chan int, out *chan int, prime int) {
for {
i := <-in // Receive value of new variable 'i' from 'in'.
if i % prime != 0 {
out -< i // Send 'i' to channel 'out'.
}
}
}
// The prime sieve: Daisy-chain Filter processes together.
func main() {
ch := new(chan int); // Create a new channel.
go Generate(ch); // Start Generate() as a goroutine.
for {
prime := <-ch;
print(prime, "\n");
ch1 := new(chan int);
go Filter(ch, ch1, prime);
ch = ch1
}
}

49
doc/progs/sieve1.go Normal file
View File

@ -0,0 +1,49 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
// Send the sequence 2, 3, 4, ... to returned channel
func Generate() *chan int {
ch := new(chan int);
go func(ch *chan int){
for i := 2; ; i++ {
ch -< i
}
}(ch);
return ch;
}
// Filter out input values divisible by 'prime', send rest to returned channel
func Filter(in *chan int, prime int) *chan int{
out := new(chan int);
go func(in *chan int, out *chan int, prime int) {
for {
if i := <-in; i % prime != 0 {
out -< i
}
}
}(in, out, prime);
return out;
}
func Sieve() *chan int {
out := new(chan int);
go func(out *chan int) {
ch := Generate();
for {
prime := <-ch;
out -< prime;
ch = Filter(ch, prime);
}
}(out);
return out;
}
func main() {
primes := Sieve();
for {
print(<-primes, "\n");
}
}