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:
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.
|
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>
|
||||||
|
|
||||||
<img src=go235.jpg >
|
<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 "-<".
|
||||||
|
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 "<-"; the expression
|
channel, and a prime number. It copies values from the input to the
|
||||||
"<-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 "<-" (receive) retrieves the next value on the channel.
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
|
||||||
|
@ -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
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