ch8: fix code format

pull/1/head
chai2010 2016-01-21 10:39:06 +08:00
parent 0b5ec941ed
commit b1730821fe
13 changed files with 393 additions and 404 deletions

View File

@ -13,31 +13,30 @@ go f() // create a new goroutine that calls f(); don't wait
在下面的例子中main goroutine會計算第45個菲波那契數。由於計算函數使用了效率非常低的遞歸所以會運行相當可觀的一段時間在這期間我們想要讓用戶看到一個可見的標識來表明程序依然在正常運行所以顯示一個動畵的小圖標
<u><i>gopl.io/ch8/spinner</i><u>
```go
gopl.io/ch8/spinner
func main() {
go spinner(100 * time.Millisecond)
const n = 45
fibN := fib(n) // slow
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
go spinner(100 * time.Millisecond)
const n = 45
fibN := fib(n) // slow
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration) {
for {
for _, r := range `-\|/` {
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
for {
for _, r := range `-\|/` {
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
func fib(x int) int {
if x < 2 {
return x
}
return fib(x-1) + fib(x-2)
if x < 2 {
return x
}
return fib(x-1) + fib(x-2)
}
```
動畵顯示了幾秒之後fib(45)的調用成功地返迴,併且打印結果:

View File

@ -4,43 +4,43 @@
我們的第一個例子是一個順序執行的時鐘服務器,它會每隔一秒鐘將當前時間寫到客戶端:
<u><i>gopl.io/ch8/clock1</i></u>
```go
gopl.io/ch8/clock1
// Clock1 is a TCP server that periodically writes the time.
package main
import (
"io"
"log"
"net"
"time"
"io"
"log"
"net"
"time"
)
func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
handleConn(conn) // handle one connection at a time
}
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
handleConn(conn) // handle one connection at a time
}
}
func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
if err != nil {
return // e.g., client disconnected
}
time.Sleep(1 * time.Second)
}
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
if err != nil {
return // e.g., client disconnected
}
time.Sleep(1 * time.Second)
}
}
```
@ -66,31 +66,31 @@ $ nc localhost 8000
客戶端將服務器發來的時間顯示了出來我們用Control+C來中斷客戶端的執行在Unix繫統上你會看到^C這樣的響應。如果你的繫統沒有裝nc這個工具你可以用telnet來實現同樣的效果或者也可以用我們下面的這個用go寫的簡單的telnet程序用net.Dial就可以簡單地創建一個TCP連接
<u><i>gopl.io/ch8/netcat1</i></u>
```go
gopl.io/ch8/netcat1
// Netcat1 is a read-only TCP client.
package main
import (
"io"
"log"
"net"
"os"
"io"
"log"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn)
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn)
}
func mustCopy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
```
@ -114,15 +114,15 @@ killall命令是一個Unix命令行工具可以用給定的進程名來殺掉
第二個客戶端必須等待第一個客戶端完成工作這樣服務端才能繼續向後執行因爲我們這里的服務器程序同一時間隻能處理一個客戶端連接。我們這里對服務端程序做一點小改動使其支持併發在handleConn函數調用的地方增加go關鍵字讓每一次handleConn的調用都進入一個獨立的goroutine。
<u><i>gopl.io/ch8/clock2</i></u>
```go
gopl.io/ch8/clock2
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
go handleConn(conn) // handle connections concurrently
conn, err := listener.Accept()
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
go handleConn(conn) // handle connections concurrently
}
```

View File

@ -4,45 +4,45 @@ clock服務器每一個連接都會起一個goroutine。在本節中我們會創
```go
func handleConn(c net.Conn) {
io.Copy(c, c) // NOTE: ignoring errors
c.Close()
io.Copy(c, c) // NOTE: ignoring errors
c.Close()
}
```
一個更有意思的echo服務應該模擬一個實際的echo的“迴響”併且一開始要用大寫HELLO來表示“聲音很大”之後經過一小段延遲返迴一個有所緩和的Hello然後一個全小寫字母的hello表示聲音漸漸變小直至消失像下面這個版本的handleConn(譯註:笑看作者腦洞大開)
<u><i>gopl.io/ch8/reverb1</i></u>
```go
gopl.io/ch8/reverb1
func echo(c net.Conn, shout string, delay time.Duration) {
fmt.Fprintln(c, "\t", strings.ToUpper(shout))
time.Sleep(delay)
fmt.Fprintln(c, "\t", shout)
time.Sleep(delay)
fmt.Fprintln(c, "\t", strings.ToLower(shout))
fmt.Fprintln(c, "\t", strings.ToUpper(shout))
time.Sleep(delay)
fmt.Fprintln(c, "\t", shout)
time.Sleep(delay)
fmt.Fprintln(c, "\t", strings.ToLower(shout))
}
func handleConn(c net.Conn) {
input := bufio.NewScanner(c)
for input.Scan() {
echo(c, input.Text(), 1*time.Second)
}
// NOTE: ignoring potential errors from input.Err()
c.Close()
input := bufio.NewScanner(c)
for input.Scan() {
echo(c, input.Text(), 1*time.Second)
}
// NOTE: ignoring potential errors from input.Err()
c.Close()
}
```
我們需要陞級我們的客戶端程序,這樣它就可以發送終端的輸入到服務器,併把服務端的返迴輸出到終端上,這使我們有了使用併發的另一個好機會:
<u><i>gopl.io/ch8/netcat2</i></u>
```go
gopl.io/ch8/netcat2
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
go mustCopy(os.Stdout, conn)
mustCopy(conn, os.Stdin)
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
go mustCopy(os.Stdout, conn)
mustCopy(conn, os.Stdin)
}
```
@ -74,15 +74,15 @@ $ killall reverb1
註意客戶端的第三次shout在前一個shout處理完成之前一直沒有被處理這貌似看起來不是特别“現實”。眞實世界里的迴響應該是會由三次shout的迴聲組合而成的。爲了模擬眞實世界的迴響我們需要更多的goroutine來做這件事情。這樣我們就再一次地需要go這個關鍵詞了這次我們用它來調用echo
<u><i>gopl.io/ch8/reverb2</i></u>
```go
gopl.io/ch8/reverb2
func handleConn(c net.Conn) {
input := bufio.NewScanner(c)
for input.Scan() {
go echo(c, input.Text(), 1*time.Second)
}
// NOTE: ignoring potential errors from input.Err()
c.Close()
input := bufio.NewScanner(c)
for input.Scan() {
go echo(c, input.Text(), 1*time.Second)
}
// NOTE: ignoring potential errors from input.Err()
c.Close()
}
```

View File

@ -10,8 +10,8 @@
在8.3節的客戶端程序它在主goroutine中譯註就是執行main函數的goroutine將標準輸入複製到server因此當客戶端程序關閉標準輸入時後台goroutine可能依然在工作。我們需要讓主goroutine等待後台goroutine完成工作後再退出我們使用了一個channel來同步兩個goroutine
<u><i>gopl.io/ch8/netcat3</i></u>
```Go
gopl.io/ch8/netcat3
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {

View File

@ -6,9 +6,8 @@ Channels也可以用於將多個goroutine鏈接在一起一個Channels的輸
第一個goroutine是一個計數器用於生成0、1、2、……形式的整數序列然後通過channel將該整數序列發送給第二個goroutine第二個goroutine是一個求平方的程序對收到的每個整數求平方然後將平方後的結果通過第二個channel發送給第三個goroutine第三個goroutine是一個打印程序打印收到的每個整數。爲了保持例子清晰我們有意選擇了非常簡單的函數當然三個goroutine的計算很簡單在現實中確實沒有必要爲如此簡單的運算構建三個goroutine。
<u><i>gopl.io/ch8/pipeline1</i></u>
```Go
gopl.io/ch8/pipeline1
func main() {
naturals := make(chan int)
squares := make(chan int)
@ -65,9 +64,8 @@ go func() {
在下面的改進中我們的計數器goroutine隻生成100個含數字的序列然後關閉naturals對應的channel這將導致計算平方數的squarer對應的goroutine可以正常終止循環併關閉squares對應的channel。在一個更複雜的程序中可以通過defer語句關閉對應的channel。最後主goroutine也可以正常終止循環併退出程序。
<u><i>gopl.io/ch8/pipeline2</i></u>
```Go
gopl.io/ch8/pipeline2
func main() {
naturals := make(chan int)
squares := make(chan int)

View File

@ -18,8 +18,8 @@ func printer(in chan int)
這是改進的版本這一次參數使用了單方向channel類型
<u><i>gopl.io/ch8/pipeline3</i></u>
```Go
gopl.io/ch8/pipeline3
func counter(out chan<- int) {
for x := 0; x < 100; x++ {
out <- x

View File

@ -2,7 +2,6 @@
帶緩存的Channel內部持有一個元素隊列。隊列的最大容量是在調用make函數創建channel時通過第二個參數指定的。下面的語句創建了一個可以持有三個字符串元素的帶緩存Channel。圖8.2是ch變量對應的channel的圖形表示形式。
```Go
ch = make(chan string, 3)
```

View File

@ -2,9 +2,10 @@
本節中我們會探索一些用來在併行時循環迭代的常見併發模型。我們會探究從全尺寸圖片生成一些縮略圖的問題。gopl.io/ch8/thumbnail包提供了ImageFile函數來幫我們拉伸圖片。我們不會説明這個函數的實現隻需要從gopl.io下載它。
<u><i>gopl.io/ch8/thumbnail</i></u>
```go
gopl.io/ch8/thumbnail
package thumbnail
// ImageFile reads an image from infile and writes
// a thumbnail-size version of it in the same directory.
// It returns the generated file name, e.g., "foo.thumb.jpg".
@ -13,15 +14,15 @@ func ImageFile(infile string) (string, error)
下面的程序會循環迭代一些圖片文件名,併爲每一張圖片生成一個縮略圖:
<u><i>gopl.io/ch8/thumbnail</i></u>
```go
gopl.io/ch8/thumbnail
// makeThumbnails makes thumbnails of the specified files.
func makeThumbnails(filenames []string) {
for _, f := range filenames {
if _, err := thumbnail.ImageFile(f); err != nil {
log.Println(err)
}
}
for _, f := range filenames {
if _, err := thumbnail.ImageFile(f); err != nil {
log.Println(err)
}
}
}
```
@ -32,9 +33,9 @@ func makeThumbnails(filenames []string) {
```go
// NOTE: incorrect!
func makeThumbnails2(filenames []string) {
for _, f := range filenames {
go thumbnail.ImageFile(f) // NOTE: ignoring errors
}
for _, f := range filenames {
go thumbnail.ImageFile(f) // NOTE: ignoring errors
}
}
```
@ -45,29 +46,28 @@ func makeThumbnails2(filenames []string) {
```go
// makeThumbnails3 makes thumbnails of the specified files in parallel.
func makeThumbnails3(filenames []string) {
ch := make(chan struct{})
for _, f := range filenames {
go func(f string) {
thumbnail.ImageFile(f) // NOTE: ignoring errors
ch <- struct{}{}
}(f)
}
// Wait for goroutines to complete.
for range filenames {
<-ch
}
ch := make(chan struct{})
for _, f := range filenames {
go func(f string) {
thumbnail.ImageFile(f) // NOTE: ignoring errors
ch <- struct{}{}
}(f)
}
// Wait for goroutines to complete.
for range filenames {
<-ch
}
}
```
註意我們將f的值作爲一個顯式的變量傳給了函數而不是在循環的閉包中聲明
```go
for _, f := range filenames {
go func() {
thumbnail.ImageFile(f) // NOTE: incorrect!
// ...
}()
go func() {
thumbnail.ImageFile(f) // NOTE: incorrect!
// ...
}()
}
```
@ -79,22 +79,22 @@ for _, f := range filenames {
// makeThumbnails4 makes thumbnails for the specified files in parallel.
// It returns an error if any step failed.
func makeThumbnails4(filenames []string) error {
errors := make(chan error)
errors := make(chan error)
for _, f := range filenames {
go func(f string) {
_, err := thumbnail.ImageFile(f)
errors <- err
}(f)}
}
for _, f := range filenames {
go func(f string) {
_, err := thumbnail.ImageFile(f)
errors <- err
}(f)}
}
for range filenames {
if err := <-errors; err != nil {
return err // NOTE: incorrect: goroutine leak!
}
}
for range filenames {
if err := <-errors; err != nil {
return err // NOTE: incorrect: goroutine leak!
}
}
return nil
return nil
}
```
@ -108,31 +108,30 @@ func makeThumbnails4(filenames []string) error {
// makeThumbnails5 makes thumbnails for the specified files in parallel.
// It returns the generated file names in an arbitrary order,
// or an error if any step failed.
func makeThumbnails5(filenames []string) (thumbfiles []string, err error) {
type item struct {
thumbfile string
err error
}
type item struct {
thumbfile string
err error
}
ch := make(chan item, len(filenames))
for _, f := range filenames {
go func(f string) {
var it item
it.thumbfile, it.err = thumbnail.ImageFile(f)
ch <- it
}(f)
}
ch := make(chan item, len(filenames))
for _, f := range filenames {
go func(f string) {
var it item
it.thumbfile, it.err = thumbnail.ImageFile(f)
ch <- it
}(f)
}
for range filenames {
it := <-ch
if it.err != nil {
return nil, it.err
}
thumbfiles = append(thumbfiles, it.thumbfile)
}
for range filenames {
it := <-ch
if it.err != nil {
return nil, it.err
}
thumbfiles = append(thumbfiles, it.thumbfile)
}
return thumbfiles, nil
return thumbfiles, nil
}
```
@ -144,35 +143,34 @@ func makeThumbnails5(filenames []string) (thumbfiles []string, err error) {
// makeThumbnails6 makes thumbnails for each file received from the channel.
// It returns the number of bytes occupied by the files it creates.
func makeThumbnails6(filenames <-chan string) int64 {
sizes := make(chan int64)
var wg sync.WaitGroup // number of working goroutines
for f := range filenames {
wg.Add(1)
// worker
go func(f string) {
defer wg.Done()
thumb, err := thumbnail.ImageFile(f)
if err != nil {
log.Println(err)
return
}
info, _ := os.Stat(thumb) // OK to ignore error
sizes <- info.Size()
}(f)
}
sizes := make(chan int64)
var wg sync.WaitGroup // number of working goroutines
for f := range filenames {
wg.Add(1)
// worker
go func(f string) {
defer wg.Done()
thumb, err := thumbnail.ImageFile(f)
if err != nil {
log.Println(err)
return
}
info, _ := os.Stat(thumb) // OK to ignore error
sizes <- info.Size()
}(f)
}
// closer
go func() {
wg.Wait()
close(sizes)
}()
// closer
go func() {
wg.Wait()
close(sizes)
}()
var total int64
for size := range sizes {
total += size
}
return total
var total int64
for size := range sizes {
total += size
}
return total
}
```

View File

@ -2,15 +2,15 @@
在5.6節中我們做了一個簡單的web爬蟲用bfs(廣度優先)算法來抓取整個網站。在本節中我們會讓這個這個爬蟲併行化這樣每一個彼此獨立的抓取命令可以併行進行IO最大化利用網絡資源。crawl函數和gopl.io/ch5/findlinks3中的是一樣的。
<u><i>gopl.io/ch8/crawl1</i></u>
```go
gopl.io/ch8/crawl1
func crawl(url string) []string {
fmt.Println(url)
list, err := links.Extract(url)
if err != nil {
log.Print(err)
}
return list
fmt.Println(url)
list, err := links.Extract(url)
if err != nil {
log.Print(err)
}
return list
}
```
@ -18,23 +18,23 @@ func crawl(url string) []string {
```go
func main() {
worklist := make(chan []string)
worklist := make(chan []string)
// Start with the command-line arguments.
go func() { worklist <- os.Args[1:] }()
// Start with the command-line arguments.
go func() { worklist <- os.Args[1:] }()
// Crawl the web concurrently.
seen := make(map[string]bool)
for list := range worklist {
for _, link := range list {
if !seen[link] {
seen[link] = true
go func(link string) {
worklist <- crawl(link)
}(link)
}
}
}
// Crawl the web concurrently.
seen := make(map[string]bool)
for list := range worklist {
for _, link := range list {
if !seen[link] {
seen[link] = true
go func(link string) {
worklist <- crawl(link)
}(link)
}
}
}
}
```
@ -52,8 +52,7 @@ https://golang.org/doc/
https://golang.org/blog/
...
2015/07/15 18:22:12 Get ...: dial tcp: lookup blog.golang.org: no such host
2015/07/15 18:22:12 Get ...: dial tcp 23.21.222.120:443: socket:
too many open files
2015/07/15 18:22:12 Get ...: dial tcp 23.21.222.120:443: socket: too many open files
...
```
@ -65,54 +64,51 @@ https://golang.org/blog/
讓我們重寫crawl函數將對links.Extract的調用操作用獲取、釋放token的操作包裹起來來確保同一時間對其隻有20個調用。信號量數量和其能操作的IO資源數量應保持接近。
<u><i>gopl.io/ch8/crawl2</i></u>
```go
gopl.io/ch8/crawl2
// tokens is a counting semaphore used to
// enforce a limit of 20 concurrent requests.
var tokens = make(chan struct{}, 20)
func crawl(url string) []string {
fmt.Println(url)
tokens <- struct{}{} // acquire a token
list, err := links.Extract(url)
<-tokens // release the token
if err != nil {
log.Print(err)
}
return list
fmt.Println(url)
tokens <- struct{}{} // acquire a token
list, err := links.Extract(url)
<-tokens // release the token
if err != nil {
log.Print(err)
}
return list
}
```
第二個問題是這個程序永遠都不會終止,卽使它已經爬到了所有初始鏈接衍生出的鏈接。(當然除非你慎重地選擇了合適的初始化URL或者已經實現了練習8.6中的深度限製,你應該還沒有意識到這個問題)。爲了使這個程序能夠終止我們需要在worklist爲空或者沒有crawl的goroutine在運行時退出主循環。
```go
func main() {
worklist := make(chan []string)
var n int // number of pending sends to worklist
worklist := make(chan []string)
var n int // number of pending sends to worklist
// Start with the command-line arguments.
n++
go func() { worklist <- os.Args[1:] }()
// Start with the command-line arguments.
n++
go func() { worklist <- os.Args[1:] }()
// Crawl the web concurrently.
seen := make(map[string]bool)
// Crawl the web concurrently.
seen := make(map[string]bool)
for ; n > 0; n-- {
list := <-worklist
for _, link := range list {
if !seen[link] {
seen[link] = true
n++
go func(link string) {
worklist <- crawl(link)
}(link)
}
}
}
for ; n > 0; n-- {
list := <-worklist
for _, link := range list {
if !seen[link] {
seen[link] = true
n++
go func(link string) {
worklist <- crawl(link)
}(link)
}
}
}
}
```
這個版本中計算器n對worklist的發送操作數量進行了限製。每一次我們發現有元素需要被發送到worklist時我們都會對n進行++操作在向worklist中發送初始的命令行參數之前我們也進行過一次++操作。這里的操作++是在每啟動一個crawler的goroutine之前。主循環會在n減爲0時終止這時候説明沒活可榦了。

View File

@ -2,27 +2,27 @@
下面的程序會進行火箭發射的倒計時。time.Tick函數返迴一個channel程序會週期性地像一個節拍器一樣向這個channel發送事件。每一個事件的值是一個時間戳不過更有意思的是其傳送方式。
<u><i>gopl.io/ch8/countdown1</i></u>
```go
gopl.io/ch8/countdown1
func main() {
fmt.Println("Commencing countdown.")
tick := time.Tick(1 * time.Second)
for countdown := 10; countdown > 0; countdown-- {
fmt.Println(countdown)
j<-tick
}
launch()
fmt.Println("Commencing countdown.")
tick := time.Tick(1 * time.Second)
for countdown := 10; countdown > 0; countdown-- {
fmt.Println(countdown)
j<-tick
}
launch()
}
```
現在我們讓這個程序支持在倒計時中用戶按下return鍵時直接中斷發射流程。首先我們啟動一個goroutine這個goroutine會嚐試從標準輸入中調入一個單獨的byte併且如果成功了會向名爲abort的channel發送一個值。
<u><i>gopl.io/ch8/countdown2</i></u>
```go
gopl.io/ch8/countdown2
abort := make(chan struct{})
go func() {
os.Stdin.Read(make([]byte, 1)) // read a single byte
abort <- struct{}{}
os.Stdin.Read(make([]byte, 1)) // read a single byte
abort <- struct{}{}
}()
```
@ -31,13 +31,13 @@ go func() {
```go
select {
case <-ch1:
// ...
// ...
case x := <-ch2:
// ...use x...
// ...use x...
case ch3 <- y:
// ...
// ...
default:
// ...
// ...
}
```
@ -47,20 +47,19 @@ select會等待case中有能夠執行的case時去執行。當條件滿足時
讓我們迴到我們的火箭發射程序。time.After函數會立卽返迴一個channel併起一個新的goroutine在經過特定的時間後向該channel發送一個獨立的值。下面的select語句會會一直等待到兩個事件中的一個到達無論是abort事件或者一個10秒經過的事件。如果10秒經過了還沒有abort事件進入那麽火箭就會發射。
```go
func main() {
// ...create abort channel...
// ...create abort channel...
fmt.Println("Commencing countdown. Press return to abort.")
select {
case <-time.After(10 * time.Second):
// Do nothing.
case <-abort:
fmt.Println("Launch aborted!")
return
}
launch()
fmt.Println("Commencing countdown. Press return to abort.")
select {
case <-time.After(10 * time.Second):
// Do nothing.
case <-abort:
fmt.Println("Launch aborted!")
return
}
launch()
}
```
@ -70,11 +69,11 @@ func main() {
```go
ch := make(chan int, 1)
for i := 0; i < 10; i++ {
select {
case x := <-ch:
fmt.Println(x) // "0" "2" "4" "6" "8"
case ch <- i:
}
select {
case x := <-ch:
fmt.Println(x) // "0" "2" "4" "6" "8"
case ch <- i:
}
}
```
@ -82,24 +81,24 @@ for i := 0; i < 10; i++ {
下面讓我們的發射程序打印倒計時。這里的select語句會使每次循環迭代等待一秒來執行退出操作。
<u><i>gopl.io/ch8/countdown3</i></u>
```go
gopl.io/ch8/countdown3
func main() {
// ...create abort channel...
// ...create abort channel...
fmt.Println("Commencing countdown. Press return to abort.")
tick := time.Tick(1 * time.Second)
for countdown := 10; countdown > 0; countdown-- {
fmt.Println(countdown)
select {
case <-tick:
// Do nothing.
case <-abort:
fmt.Println("Launch aborted!")
return
}
}
launch()
fmt.Println("Commencing countdown. Press return to abort.")
tick := time.Tick(1 * time.Second)
for countdown := 10; countdown > 0; countdown-- {
fmt.Println(countdown)
select {
case <-tick:
// Do nothing.
case <-abort:
fmt.Println("Launch aborted!")
return
}
}
launch()
}
```
@ -120,10 +119,10 @@ ticker.Stop() // cause the ticker's goroutine to terminate
```go
select {
case <-abort:
fmt.Printf("Launch aborted!\n")
return
fmt.Printf("Launch aborted!\n")
return
default:
// do nothing
// do nothing
}
```

View File

@ -2,8 +2,8 @@
在本小節中我們會創建一個程序來生成指定目録的硬盤使用情況報告這個程序和Unix里的du工具比較相似。大多數工作用下面這個walkDir函數來完成這個函數使用dirents函數來枚舉一個目録下的所有入口。
<u><i>gopl.io/ch8/du1</i></u>
```go
gopl.io/ch8/du1
// walkDir recursively walks the file tree rooted at dir
// and sends the size of each found file on fileSizes.
func walkDir(dir string, fileSizes chan<- int64) {
@ -70,9 +70,8 @@ func main() {
}
func printDiskUsage(nfiles, nbytes int64) {
fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9)
fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9)
}
```
這個程序會在打印其結果之前卡住很長時間。
@ -87,8 +86,8 @@ $ ./du1 $HOME /usr /bin /etc
下面這個du的變種會間歇打印內容不過隻有在調用時提供了-v的flag才會顯示程序進度信息。在roots目録上循環的後台goroutine在這里保持不變。主goroutine現在使用了計時器來每500ms生成事件然後用select語句來等待文件大小的消息來更新總大小數據或者一個計時器的事件來打印當前的總大小數據。如果-v的flag在運行時沒有傳入的話tick這個channel會保持爲nil這樣在select里的case也就相當於被禁用了。
<u><i>gopl.io/ch8/du2</i></u>
```go
gopl.io/ch8/du2
var verbose = flag.Bool("v", false, "show verbose progress messages")
func main() {
@ -116,6 +115,7 @@ loop:
printDiskUsage(nfiles, nbytes) // final totals
}
```
由於我們的程序不再使用range循環第一個select的case必須顯式地判斷fileSizes的channel是不是已經被關閉了這里可以用到channel接收的二值形式。如果channel已經被關閉了的話程序會直接退出循環。這里的break語句用到了標籤break這樣可以同時終結select和for兩個循環如果沒有用標籤就break的話隻會退出內層的select循環而外層的for循環會使之進入下一輪select循環。
現在程序會悠閒地爲我們打印更新流:
@ -133,50 +133,49 @@ $ ./du2 -v $HOME /usr /bin /etc
然而這個程序還是會花上很長時間才會結束。無法對walkDir做併行化處理沒什麽别的原因無非是因爲磁盤繫統併行限製。下面這個第三個版本的du會對每一個walkDir的調用創建一個新的goroutine。它使用sync.WaitGroup (§8.5)來對仍舊活躍的walkDir調用進行計數另一個goroutine會在計數器減爲零的時候將fileSizes這個channel關閉。
<u><i>gopl.io/ch8/du3</i></u>
```go
gopl.io/ch8/du3
func main() {
// ...determine roots...
// Traverse each root of the file tree in parallel.
fileSizes := make(chan int64)
var n sync.WaitGroup
for _, root := range roots {
n.Add(1)
go walkDir(root, &n, fileSizes)
}
go func() {
n.Wait()
close(fileSizes)
}()
// ...select loop...
// ...determine roots...
// Traverse each root of the file tree in parallel.
fileSizes := make(chan int64)
var n sync.WaitGroup
for _, root := range roots {
n.Add(1)
go walkDir(root, &n, fileSizes)
}
go func() {
n.Wait()
close(fileSizes)
}()
// ...select loop...
}
func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
defer n.Done()
for _, entry := range dirents(dir) {
if entry.IsDir() {
n.Add(1)
subdir := filepath.Join(dir, entry.Name())
go walkDir(subdir, n, fileSizes)
} else {
fileSizes <- entry.Size()
}
}
defer n.Done()
for _, entry := range dirents(dir) {
if entry.IsDir() {
n.Add(1)
subdir := filepath.Join(dir, entry.Name())
go walkDir(subdir, n, fileSizes)
} else {
fileSizes <- entry.Size()
}
}
}
```
由於這個程序在高峯期會創建成百上韆的goroutine我們需要脩改dirents函數用計數信號量來阻止他同時打開太多的文件就像我們在8.7節中的併發爬蟲一樣:
```go
// sema is a counting semaphore for limiting concurrency in dirents.
var sema = make(chan struct{}, 20)
// dirents returns the entries of directory dir.
func dirents(dir string) []os.FileInfo {
sema <- struct{}{} // acquire token
defer func() { <-sema }() // release token
// ...
sema <- struct{}{} // acquire token
defer func() { <-sema }() // release token
// ...
```
這個版本比之前那個快了好幾倍,盡管其具體效率還是和你的運行環境,機器配置相關。

View File

@ -10,17 +10,17 @@ Go語言併沒有提供在一個goroutine中終止另一個goroutine的方法
隻要一些小脩改我們就可以把退出邏輯加入到前一節的du程序。首先我們創建一個退出的channel這個channel不會向其中發送任何值但其所在的閉包內要寫明程序需要退出。我們同時還定義了一個工具函數cancelled這個函數在被調用的時候會輪詢退出狀態。
<u><i>gopl.io/ch8/du4</i></u>
```go
gopl.io/ch8/du4
var done = make(chan struct{})
func cancelled() bool {
select {
case <-done:
return true
default:
return false
}
select {
case <-done:
return true
default:
return false
}
}
```
@ -29,8 +29,8 @@ func cancelled() bool {
```go
// Cancel traversal when input is detected.
go func() {
os.Stdin.Read(make([]byte, 1)) // read a single byte
close(done)
os.Stdin.Read(make([]byte, 1)) // read a single byte
close(done)
}()
```
@ -38,16 +38,16 @@ go func() {
```go
for {
select {
case <-done:
// Drain fileSizes to allow existing goroutines to finish.
for range fileSizes {
// Do nothing.
}
return
case size, ok := <-fileSizes:
// ...
}
select {
case <-done:
// Drain fileSizes to allow existing goroutines to finish.
for range fileSizes {
// Do nothing.
}
return
case size, ok := <-fileSizes:
// ...
}
}
```
@ -55,13 +55,13 @@ walkDir這個goroutine一啟動就會輪詢取消狀態如果取消狀態被
```go
func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
defer n.Done()
if cancelled() {
return
}
for _, entry := range dirents(dir) {
// ...
}
defer n.Done()
if cancelled() {
return
}
for _, entry := range dirents(dir) {
// ...
}
}
```
@ -71,13 +71,13 @@ func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
```go
func dirents(dir string) []os.FileInfo {
select {
case sema <- struct{}{}: // acquire token
case <-done:
return nil // cancelled
}
defer func() { <-sema }() // release token
// ...read directory...
select {
case sema <- struct{}{}: // acquire token
case <-done:
return nil // cancelled
}
defer func() { <-sema }() // release token
// ...read directory...
}
```

View File

@ -1,24 +1,25 @@
## 8.10. 示例: 聊天服務
我們用一個聊天服務器來終結本章節的內容這個程序可以讓一些用戶通過服務器向其它所有用戶廣播文本消息。這個程序中有四種goroutine。main和broadcaster各自是一個goroutine實例每一個客戶端的連接都會有一個handleConn和clientWriter的goroutine。broadcaster是select用法的不錯的樣例因爲它需要處理三種不同類型的消息。
下面演示的main goroutine的工作是listen和accept(譯註:網絡編程里的概念)從客戶端過來的連接。對每一個連接程序都會建立一個新的handleConn的goroutine就像我們在本章開頭的併發的echo服務器里所做的那樣。
<u><i>gopl.io/ch8/chat</i></u>
```go
gopl.io/ch8/chat
func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
go broadcaster()
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn)
}
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
go broadcaster()
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn)
}
}
```
@ -28,29 +29,29 @@ func main() {
type client chan<- string // an outgoing message channel
var (
entering = make(chan client)
leaving = make(chan client)
messages = make(chan string) // all incoming client messages
entering = make(chan client)
leaving = make(chan client)
messages = make(chan string) // all incoming client messages
)
func broadcaster() {
clients := make(map[client]bool) // all connected clients
for {
select {
case msg := <-messages:
// Broadcast incoming message to all
// clients' outgoing message channels.
for cli := range clients {
cli <- msg
}
case cli := <-entering:
clients[cli] = true
clients := make(map[client]bool) // all connected clients
for {
select {
case msg := <-messages:
// Broadcast incoming message to all
// clients' outgoing message channels.
for cli := range clients {
cli <- msg
}
case cli := <-entering:
clients[cli] = true
case cli := <-leaving:
delete(clients, cli)
close(cli)
}
}
case cli := <-leaving:
delete(clients, cli)
close(cli)
}
}
}
```
@ -60,29 +61,29 @@ broadcaster監聽來自全局的entering和leaving的channel來獲知客戶端
```go
func handleConn(conn net.Conn) {
ch := make(chan string) // outgoing client messages
go clientWriter(conn, ch)
ch := make(chan string) // outgoing client messages
go clientWriter(conn, ch)
who := conn.RemoteAddr().String()
ch <- "You are " + who
messages <- who + " has arrived"
entering <- ch
who := conn.RemoteAddr().String()
ch <- "You are " + who
messages <- who + " has arrived"
entering <- ch
input := bufio.NewScanner(conn)
for input.Scan() {
messages <- who + ": " + input.Text()
}
// NOTE: ignoring potential errors from input.Err()
input := bufio.NewScanner(conn)
for input.Scan() {
messages <- who + ": " + input.Text()
}
// NOTE: ignoring potential errors from input.Err()
leaving <- ch
messages <- who + " has left"
conn.Close()
leaving <- ch
messages <- who + " has left"
conn.Close()
}
func clientWriter(conn net.Conn, ch <-chan string) {
for msg := range ch {
fmt.Fprintln(conn, msg) // NOTE: ignoring network errors
}
for msg := range ch {
fmt.Fprintln(conn, msg) // NOTE: ignoring network errors
}
}
```