mirror of
https://github.com/golang/go
synced 2024-11-21 18:54:43 -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:
parent
721cb9c6a1
commit
db9002f152
@ -212,9 +212,9 @@ An Interlude about Constants
|
||||
|
||||
Although integers come in lots of sizes in Go, integer constants do not.
|
||||
There are no constants like "0ll" or "0x0UL". Instead, integer
|
||||
constants are evaluated as ideal, arbitrary precision values that
|
||||
can overflow only when they are assigned to an integer variable of
|
||||
some specific size.
|
||||
constants are evaluated as ideal, large-precision values that
|
||||
can overflow only when they are assigned to an integer variable with
|
||||
too little precision to represent the value.
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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/
|
||||
|
||||
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.
|
||||
|
||||
The prime sieve program in the language specification document is
|
||||
an excellent illustration of concurrent programming, but for variety
|
||||
here we'll solve a different problem in a similar way.
|
||||
A classic program in the style is the prime sieve of Eratosthenes.
|
||||
It works by taking a stream of all the natural numbers, and introducing
|
||||
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
|
||||
integers that can be written as multiples of 2, 3, and 5 only.
|
||||
One way to solve it is to generate streams of numbers multiplied
|
||||
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:
|
||||
Here's a flow diagram; each box represents a filter element whose
|
||||
creation is triggered by the first number that flowed from the
|
||||
elements before it.
|
||||
|
||||
<br>
|
||||
|
||||
<img src=go235.jpg >
|
||||
<img src='sieve.gif'>
|
||||
|
||||
<br>
|
||||
|
||||
To create a stream of integers, we use a Go <i>channel</i>, which,
|
||||
borrowing from CSP and its descendants, represents a communications
|
||||
channel that can connect two computations. In Go, channel variables are
|
||||
always pointers to channels -- it's the (hidden) object they point to that
|
||||
borrowing from CSP's descendants, represents a communications
|
||||
channel that can connect two concurrent computations.
|
||||
In Go, channel variables are
|
||||
always pointers to channels -- it's the object they point to that
|
||||
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,
|
||||
using the shorthand "INT" defined on line 3.
|
||||
The function "Generate" sends the sequence 2, 3, 4, 5, ... to its
|
||||
argument channel, "ch", using the binary send operator "-<".
|
||||
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
|
||||
on the channel "in", using the unary receive operator "<-"; the expression
|
||||
"<-in" retrieves the next value on the the channel. The value
|
||||
is multiplied by the factor "f" and then sent out on channel "out",
|
||||
using the binary send operator "-<". 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.
|
||||
The "Filter" function has three arguments: an input channel, an output
|
||||
channel, and a prime number. It copies values from the input to the
|
||||
output, discarding anything divisible by the prime. The unary prefix
|
||||
operator "<-" (receive) retrieves the next value on the channel.
|
||||
|
||||
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,
|
||||
so to avoid notational confusion we'll call concurrently executing
|
||||
computations in Go <i>goroutines</i>. To start a goroutine,
|
||||
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:
|
||||
|
||||
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
|
||||
result := <-ch; // wait for, and retrieve, result
|
||||
|
||||
Back to our 2-3-5 program. Here's how "main" sets up the
|
||||
calculation:
|
||||
Back to our prime sieve. Here's how the sieve pipeline is stitched
|
||||
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,
|
||||
and lines 24 through 26 launch the goroutines. The "100" parameter
|
||||
to the input channels ("c2i" etc.) is a buffer size. By default,
|
||||
Go channels are unbuffered (synchronous) but the "Multipler" inputs need to
|
||||
be buffered because the main loop will generate data faster than
|
||||
they process it.
|
||||
Line 23 creates the initial channel to pass to "Generate", which it
|
||||
then starts up. As each prime pops out of the channel, a new "Filter"
|
||||
is added to the pipeline and <i>its</i> output becomes the new value
|
||||
of "ch".
|
||||
|
||||
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
|
||||
hold the latest value received from each "Multiplier" goroutine.
|
||||
--PROG progs/sieve1.go /func.Filter/ /^}/
|
||||
|
||||
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
|
||||
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.
|
||||
Now "main"'s interface to the prime sieve is a channel of primes:
|
||||
|
||||
This program can be tightened up a little using a pattern common
|
||||
in this style of programming. Here is a variant version of "Multiplier",
|
||||
from "progs/235B.go":
|
||||
--PROG progs/sieve1.go /func.main/ /^}/
|
||||
|
||||
--PROG progs/235B.go /func.M/ /^}/
|
||||
Service
|
||||
----
|
||||
|
||||
This version does all the setup internally. It creates the channels,
|
||||
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.
|
||||
here we will describe this server:
|
||||
|
||||
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
|
||||
|
@ -11,15 +11,14 @@ import (
|
||||
|
||||
var rot13_flag = Flag.Bool("rot13", false, nil, "rot13 the input")
|
||||
|
||||
func rot13(bb byte) byte {
|
||||
var b int = int(bb) /// BUG: until byte division is fixed
|
||||
func rot13(b byte) byte {
|
||||
if 'a' <= b && b <= 'z' {
|
||||
b = 'a' + ((b - 'a') + 13) % 26;
|
||||
}
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
b = 'A' + ((b - 'A') + 13) % 26
|
||||
}
|
||||
return byte(b)
|
||||
return b
|
||||
}
|
||||
|
||||
type Reader interface {
|
||||
|
49
doc/progs/server.go
Normal file
49
doc/progs/server.go
Normal 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
55
doc/progs/server1.go
Normal 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
36
doc/progs/sieve.go
Normal 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
49
doc/progs/sieve1.go
Normal 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");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user