pull/1/head
chai2010 2016-01-18 11:14:19 +08:00
parent a91355f5f1
commit 884ada9cd0
20 changed files with 116 additions and 92 deletions

View File

@ -11,25 +11,25 @@ golang.org/x/net/html
package html
type Node struct {
Type NodeType
Data string
Attr []Attribute
FirstChild, NextSibling *Node
Type NodeType
Data string
Attr []Attribute
FirstChild, NextSibling *Node
}
type NodeType int32
const (
ErrorNode NodeType = iota
TextNode
DocumentNode
ElementNode
CommentNode
DoctypeNode
ErrorNode NodeType = iota
TextNode
DocumentNode
ElementNode
CommentNode
DoctypeNode
)
type Attribute struct {
Key, Val string
Key, Val string
}
func Parse(r io.Reader) (*Node, error)
@ -43,21 +43,21 @@ gopl.io/ch5/findlinks1
package main
import (
"fmt"
"os"
"fmt"
"os"
"golang.org/x/net/html"
"golang.org/x/net/html"
)
func main() {
doc, err := html.Parse(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err)
os.Exit(1)
}
for _, link := range visit(nil, doc) {
fmt.Println(link)
}
doc, err := html.Parse(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err)
os.Exit(1)
}
for _, link := range visit(nil, doc) {
fmt.Println(link)
}
}
```
@ -66,17 +66,17 @@ visit函數遍歷HTML的節點樹從每一個anchor元素的href屬性獲得l
```Go
// visit appends to links each link found in n and returns the result.
func visit(links []string, n *html.Node) []string {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "href" {
links = append(links, a.Val)
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
links = visit(links, c)
}
return links
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "href" {
links = append(links, a.Val)
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
links = visit(links, c)
}
return links
}
```
@ -109,21 +109,21 @@ http://www.google.com/intl/en/policies/privacy/
```Go
gopl.io/ch5/outline
func main() {
doc, err := html.Parse(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "outline: %v\n", err)
os.Exit(1)
}
outline(nil, doc)
doc, err := html.Parse(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "outline: %v\n", err)
os.Exit(1)
}
outline(nil, doc)
}
func outline(stack []string, n *html.Node) {
if n.Type == html.ElementNode {
stack = append(stack, n.Data) // push tag
fmt.Println(stack)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
outline(stack, c)
}
if n.Type == html.ElementNode {
stack = append(stack, n.Data) // push tag
fmt.Println(stack)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
outline(stack, c)
}
}
```
@ -153,10 +153,10 @@ $ ./fetch https://golang.org | ./outline
大部分編程語言使用固定大小的函數調用棧常見的大小從64KB到2MB不等。固定大小棧會限製遞歸的深度當你用遞歸處理大量數據時需要避免棧溢出除此之外還會導致安全性問題。與相反,Go語言使用可變棧棧的大小按需增加(初始時很小)。這使得我們使用遞歸時不必考慮溢出和安全問題。
練習**5.1** :脩改findlinks代碼中遍歷n.FirstChild鏈表的部分將循環調用visit改成遞歸調用。
**練習 5.1** 脩改findlinks代碼中遍歷n.FirstChild鏈表的部分將循環調用visit改成遞歸調用。
練習**5.2** : 編寫函數記録在HTML樹中出現的同名元素的次數。
**練習 5.2** 編寫函數記録在HTML樹中出現的同名元素的次數。
練習**5.3** : 編寫函數輸出所有text結點的內容。註意不要訪問`<script>``<style>`,
**練習 5.3** 編寫函數輸出所有text結點的內容。註意不要訪問`<script>``<style>`,
練習**5.4** : 擴展vist函數使其能夠處理其他類型的結點如images、scripts和style sheets。
**練習 5.4** 擴展vist函數使其能夠處理其他類型的結點如images、scripts和style sheets。

View File

@ -21,7 +21,7 @@
```Go
var f func(int) int
f(3) // 此處f的值爲nil,會引起panic錯誤
f(3) // 此處f的值爲nil, 會引起panic錯誤
```
函數值可以與nil比較

View File

@ -120,7 +120,7 @@ func trace(msg string) func() {
每一次bigSlowOperation被調用程序都會記録函數的進入退出持續時間。我們用time.Sleep模擬一個耗時的操作
```powershell
```
$ go build gopl.io/ch5/trace
$ ./trace
2015/11/18 09:53:26 enter bigSlowOperation

View File

@ -4,7 +4,7 @@ Go的類型繫統會在編譯時捕獲很多錯誤但有些錯誤隻能在運
一般而言當panic異常發生時程序會中斷運行併立卽執行在該goroutine可以先理解成線程在第8章會詳細介紹中被延遲的函數defer 機製。隨後程序崩潰併輸出日誌信息。日誌信息包括panic value和函數調用的堆棧跟蹤信息。panic value通常是某種錯誤信息。對於每個goroutine日誌信息中都會有與之相對的發生panic時的函數調用堆棧跟蹤信息。通常我們不需要再次運行程序去定位問題日誌信息已經提供了足夠的診斷依據。因此在我們填寫問題報告時一般會將panic異常和日誌信息一併記録。
不是所有的panic異常都來自運行時直接調用內置的panic函數也會引發panic異常panic函數接受任何值作爲參數。 當某些不應該發生的場景發生時我們就應該調用panic。比如當程序到達了某條邏輯上不可能到達的路徑
不是所有的panic異常都來自運行時直接調用內置的panic函數也會引發panic異常panic函數接受任何值作爲參數。當某些不應該發生的場景發生時我們就應該調用panic。比如當程序到達了某條邏輯上不可能到達的路徑
```Go
switch s := suit(drawCard()); s {
@ -67,7 +67,7 @@ func f(x int) {
上例中的運行輸出如下:
```bash
```
f(3)
f(2)
f(1)
@ -78,7 +78,7 @@ defer 3
當f(0)被調用時發生panic異常之前被延遲執行的的3個fmt.Printf被調用。程序中斷執行後panic信息和堆棧信息會被輸出下面是簡化的輸出
```powershell
```
panic: runtime error: integer divide by zero
main.f(0)
src/gopl.io/ch5/defer1/defer.go:14
@ -111,7 +111,7 @@ func printStack() {
printStack的簡化輸出如下下面隻是printStack的輸出不包括panic的日誌信息
```bash
```
goroutine 1 [running]:
main.printStack()
src/gopl.io/ch5/defer2/defer.go:20

View File

@ -87,4 +87,4 @@ fmt.Println(geometry.PathDistance(perim)) // "12", standalone function
fmt.Println(perim.Distance()) // "12", method of geometry.Path
```
譯註如果我們要用方法去計算perim的distance還需要去寫全geometry的包名和其函數名但是因爲Path這個變量定義了一個可以直接用的Distance方法所以我們可以直接寫perim.Distance()。相當於可以少打很多字作者應該是這個意思。因爲在Go里包外調用函數需要帶上包名還是挺麻煩的。
**譯註:** 如果我們要用方法去計算perim的distance還需要去寫全geometry的包名和其函數名但是因爲Path這個變量定義了一個可以直接用的Distance方法所以我們可以直接寫perim.Distance()。相當於可以少打很多字作者應該是這個意思。因爲在Go里包外調用函數需要帶上包名還是挺麻煩的。

View File

@ -63,4 +63,4 @@ m.Add("item", "3") // panic: assignment to entry in nil map
對Get的最後一次調用中nil接收器的行爲卽是一個空map的行爲。我們可以等價地將這個操作寫成Value(nil).Get("item")但是如果你直接寫nil.Get("item")的話是無法通過編譯的因爲nil的字面量編譯器無法判斷其準備類型。所以相比之下最後的那行m.Add的調用就會産生一個panic因爲他嚐試更新一個空map。
由於url.Values是一個map類型併且間接引用了其key/value對因此url.Values.Add對這個map里的元素做任何的更新、刪除操作對調用方都是可見的。實際上就像在普通函數中一樣雖然可以通過引用來操作內部值但在方法想要脩改引用本身是不會影響原始值的比如把他置爲nil或者讓這個引用指向了其它的對象調用方都不會受影響。(譯註因爲傳入的是存儲了內存地址的變量你改變這個變量是影響不了原始的變量的想想C語言是差不多的)
由於url.Values是一個map類型併且間接引用了其key/value對因此url.Values.Add對這個map里的元素做任何的更新、刪除操作對調用方都是可見的。實際上就像在普通函數中一樣雖然可以通過引用來操作內部值但在方法想要脩改引用本身是不會影響原始值的比如把他置爲nil或者讓這個引用指向了其它的對象調用方都不會受影響。譯註因爲傳入的是存儲了內存地址的變量你改變這個變量是影響不了原始的變量的想想C語言是差不多的

View File

@ -85,9 +85,9 @@ pptr.Distance(q) // implicit (*pptr)
如果類型T的所有方法都是用T類型自己來做接收器(而不是`*T`)那麽拷貝這種類型的實例就是安全的調用他的任何一個方法也就會産生一個值的拷貝。比如time.Duration的這個類型在調用其方法時就會被全部拷貝一份包括在作爲參數傳入函數的時候。但是如果一個方法使用指針作爲接收器你需要避免對其進行拷貝因爲這樣可能會破壞掉該類型內部的不變性。比如你對bytes.Buffer對象進行了拷貝那麽可能會引起原始對象和拷貝對象隻是别名而已但實際上其指向的對象是一致的。緊接着對拷貝後的變量進行脩改可能會有讓你意外的結果。
譯註:作者這里説的比較繞,其實有兩點:
**譯註:** 作者這里説的比較繞,其實有兩點:
1.不管你的method的receiver是指針類型還是非指針類型都是可以通過指針/非指針類型進行調用的,編譯器會幫你做類型轉換
2.在聲明一個method的receiver該是指針還是非指針類型時你需要考慮兩方面的內部第一方面是這個對象本身是不是特别大如果聲明爲非指針變量時調用會産生一次拷貝第二方面是如果你用指針類型作爲receiver那麽你一定要註意這種指針類型指向的始終是一塊內存地址就算你對其進行了拷貝。熟悉C或者C艹的人這里應該很快能明白。
1. 不管你的method的receiver是指針類型還是非指針類型都是可以通過指針/非指針類型進行調用的,編譯器會幫你做類型轉換
2. 在聲明一個method的receiver該是指針還是非指針類型時你需要考慮兩方面的內部第一方面是這個對象本身是不是特别大如果聲明爲非指針變量時調用會産生一次拷貝第二方面是如果你用指針類型作爲receiver那麽你一定要註意這種指針類型指向的始終是一塊內存地址就算你對其進行了拷貝。熟悉C或者C艹的人這里應該很快能明白。
{% include "./ch6-02-1.md" %}

View File

@ -105,9 +105,9 @@ func (*IntSet) Clear() // remove all elements from the set
func (*IntSet) Copy() *IntSet // return a copy of the set
```
練習6.2: 定義一個變參方法(*IntSet).AddAll(...int)這個方法可以爲一組IntSet值求和比如s.AddAll(1,2,3)。
**練習 6.2** 定義一個變參方法(*IntSet).AddAll(...int)這個方法可以爲一組IntSet值求和比如s.AddAll(1,2,3)。
練習6.3: (*IntSet).UnionWith會用|操作符計算兩個集合的交集我們再爲IntSet實現另外的幾個函數IntersectWith(交集元素在A集合B集合均出現),DifferenceWith(差集元素出現在A集合未出現在B集合),SymmetricDifference(併差集元素出現在A但沒有出現在B或者出現在B沒有出現在A)。
**練習 6.3** (*IntSet).UnionWith會用|操作符計算兩個集合的交集我們再爲IntSet實現另外的幾個函數IntersectWith(交集元素在A集合B集合均出現),DifferenceWith(差集元素出現在A集合未出現在B集合),SymmetricDifference(併差集元素出現在A但沒有出現在B或者出現在B沒有出現在A)。
練習6.4: 實現一個Elems方法返迴集合中的所有元素用於做一些range之類的遍歷操作。
練習6.5: 我們這章定義的IntSet里的每個字都是用的uint64類型但是64位的數值可能在32位的平台上不高效。脩改程序使其使用uint類型這種類型對於32位平台來説更合適。當然了這里我們可以不用簡單粗暴地除64可以定義一個常量來決定是用32還是64這里你可能會用到平台的自動判斷的一個智能表達式32 << (^uint(0) >> 63)
**練習 6.5** 我們這章定義的IntSet里的每個字都是用的uint64類型但是64位的數值可能在32位的平台上不高效。脩改程序使其使用uint類型這種類型對於32位平台來説更合適。當然了這里我們可以不用簡單粗暴地除64可以定義一個常量來決定是用32還是64這里你可能會用到平台的自動判斷的一個智能表達式32 << (^uint(0) >> 63)

View File

@ -79,12 +79,12 @@ type Stringer interface {
我們會在7.10節解釋fmt包怎麽發現哪些值是滿足這個接口類型的。
練習7.1:使用來自ByteCounter的思路實現一個針對對單詞和行數的計數器。你會發現bufio.ScanWords非常的有用。
**練習 7.1** 使用來自ByteCounter的思路實現一個針對對單詞和行數的計數器。你會發現bufio.ScanWords非常的有用。
練習7.2:寫一個帶有如下函數籤名的函數CountingWriter傳入一個io.Writer接口類型返迴一個新的Writer類型把原來的Writer封裝在里面和一個表示寫入新的Writer字節數的int64類型指針
**練習 7.2** 寫一個帶有如下函數籤名的函數CountingWriter傳入一個io.Writer接口類型返迴一個新的Writer類型把原來的Writer封裝在里面和一個表示寫入新的Writer字節數的int64類型指針
```go
func CountingWriter(w io.Writer) (io.Writer, *int64)
```
練習7.3:爲在gopl.io/ch4/treesort (§4.4)的*tree類型實現一個String方法去展示tree類型的值序列。
**練習 7.3** 爲在gopl.io/ch4/treesort (§4.4)的*tree類型實現一個String方法去展示tree類型的值序列。

View File

@ -46,9 +46,9 @@ type ReadWriter interface {
上面3種定義方式都是一樣的效果。方法的順序變化也沒有影響唯一重要的就是這個集合里面的方法。
練習7.4:strings.NewReader函數通過讀取一個string參數返迴一個滿足io.Reader接口類型的值和其它值。實現一個簡單版本的NewReader併用它來構造一個接收字符串輸入的HTML解析器§5.2
**練習 7.4** strings.NewReader函數通過讀取一個string參數返迴一個滿足io.Reader接口類型的值和其它值。實現一個簡單版本的NewReader併用它來構造一個接收字符串輸入的HTML解析器§5.2
練習7.5:io包里面的LimitReader函數接收一個io.Reader接口類型的r和字節數n併且返迴另一個從r中讀取字節但是當讀完n個字節後就表示讀到文件結束的Reader。實現這個LimitReader函數
**練習 7.5** io包里面的LimitReader函數接收一個io.Reader接口類型的r和字節數n併且返迴另一個從r中讀取字節但是當讀完n個字節後就表示讀到文件結束的Reader。實現這個LimitReader函數
```go
func LimitReader(r io.Reader, n int64) io.Reader

View File

@ -1,5 +1,5 @@
## 7.3. 實現接口的條件
一個類型如果擁有一個接口需要的所有方法,那麽這個類型就實現了這個接口。例如,*os.File類型實現了io.ReaderWriterCloser和ReadWriter接口。*bytes.Buffer實現了ReaderWriter和ReadWriter這些接口但是它沒有實現Closer接口因爲它不具有Close方法。Go的程序員經常會簡要的把一個具體的類型描述成一個特定的接口類型。舉個例子*bytes.Buffer是io.Writer*os.Files是io.ReadWriter。
一個類型如果擁有一個接口需要的所有方法,那麽這個類型就實現了這個接口。例如,\*os.File類型實現了io.ReaderWriterCloser和ReadWriter接口。\*bytes.Buffer實現了ReaderWriter和ReadWriter這些接口但是它沒有實現Closer接口因爲它不具有Close方法。Go的程序員經常會簡要的把一個具體的類型描述成一個特定的接口類型。舉個例子\*bytes.Buffer是io.Writer\*os.Files是io.ReadWriter。
接口指定的規則非常簡單:表達一個類型屬於某個接口隻要這個類型實現這個接口。所以:
@ -13,34 +13,44 @@ var rwc io.ReadWriteCloser
rwc = os.Stdout // OK: *os.File has Read, Write, Close methods
rwc = new(bytes.Buffer) // compile error: *bytes.Buffer lacks Close method
```
這個規則甚至適用於等式右邊本身也是一個接口類型
```go
w = rwc // OK: io.ReadWriteCloser has Write method
rwc = w // compile error: io.Writer lacks Close method
```
因爲ReadWriter和ReadWriteCloser包含所有Writer的方法所以任何實現了ReadWriter和ReadWriteCloser的類型必定也實現了Writer接口
在進一步學習前必鬚先解釋表示一個類型持有一個方法當中的細節。迴想在6.2章中對於每一個命名過的具體類型T它一些方法的接收者是類型T本身然而另一些則是一個*T的指針。還記得在T類型的參數上調用一個*T的方法是合法的隻要這個參數是一個變量編譯器隱式的獲取了它的地址。但這僅僅是一個語法醣T類型的值不擁有所有*T指針的方法那這樣它就可能隻實現更少的接口。
舉個例子可能會更清晰一點。在第6.5章中IntSet類型的String方法的接收者是一個指針類型所以我們不能在一個不能尋址的IntSet值上調用這個方法
```go
type IntSet struct { /* ... */ }
func (*IntSet) String() string
var _ = IntSet{}.String() // compile error: String requires *IntSet receiver
```
但是我們可以在一個IntSet值上調用這個方法
```go
var s IntSet
var _ = s.String() // OK: s is a variable and &s has a String method
```
然而,由於隻有*IntSet類型有String方法所有也隻有*IntSet類型實現了fmt.Stringer接口
```go
var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // compile error: IntSet lacks String method
```
12.8章包含了一個打印出任意值的所有方法的程序然後可以使用godoc -analysis=type tool(§10.7.4)展示每個類型的方法和具體類型和接口之間的關繫
就像信封封裝和隱藏信件起來一樣,接口類型封裝和隱藏具體類型和它的值。卽使具體類型有其它的方法也隻有接口類型暴露出來的方法會被調用到:
```go
os.Stdout.Write([]byte("hello")) // OK: *os.File has Write method
os.Stdout.Close() // OK: *os.File has Close method
@ -50,9 +60,11 @@ w = os.Stdout
w.Write([]byte("hello")) // OK: io.Writer has Write method
w.Close() // compile error: io.Writer lacks Close method
```
一個有更多方法的接口類型比如io.ReadWriter和少一些方法的接口類型,例如io.Reader進行對比更多方法的接口類型會告訴我們更多關於它的值持有的信息併且對實現它的類型要求更加嚴格。那麽關於interface{}類型,它沒有任何方法,請講出哪些具體的類型實現了它?
這看上去好像沒有用但實際上interface{}被稱爲空接口類型是不可或缺的。因爲空接口類型對實現它的類型沒有要求,所以我們可以將任意一個值賦給空接口類型。
```go
var any interface{}
any = true
@ -61,26 +73,32 @@ any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)
```
盡管不是很明顯從本書最早的的例子中我們就已經在使用空接口類型。它允許像fmt.Println或者5.7章中的errorf函數接受任何類型的參數。
對於創建的一個interface{}值持有一個booleanfloatstringmappointer或者任意其它的類型我們當然不能直接對它持有的值做操作因爲interface{}沒有任何方法。我們會在7.10章中學到一種用類型斷言來獲取interface{}中值的方法。
因爲接口實現隻依賴於判斷的兩個類型的方法,所以沒有必要定義一個具體類型和它實現的接口之間的關繫。也就是説,嚐試文檔化和斷言這種關繫幾乎沒有用,所以併沒有通過程序強製定義。下面的定義在編譯期斷言一個*bytes.Buffer的值實現了io.Writer接口類型:
```go
// *bytes.Buffer must satisfy io.Writer
var w io.Writer = new(bytes.Buffer)
```
因爲任意*bytes.Buffer的值甚至包括nil通過(*bytes.Buffer)(nil)進行顯示的轉換都實現了這個接口所以我們不必分配一個新的變量。併且因爲我們絶不會引用變量w我們可以使用空標識符來來進行代替。總的看這些變化可以讓我們得到一個更樸素的版本
```go
// *bytes.Buffer must satisfy io.Writer
var _ io.Writer = (*bytes.Buffer)(nil)
```
非空的接口類型比如io.Writer經常被指針類型實現尤其當一個或多個接口方法像Write方法那樣隱式的給接收者帶來變化的時候。一個結構體的指針是非常常見的承載方法的類型。
但是併不意味着隻有指針類型滿足接口類型甚至連一些有設置方法的接口類型也可能會被Go語言中其它的引用類型實現。我們已經看過slice類型的方法(geometry.Path, §6.1)和map類型的方法(url.Values, §6.2.1),後面還會看到函數類型的方法的例子(http.HandlerFunc, §7.7)。甚至基本的類型也可能會實現一些接口就如我們在7.4章中看到的time.Duration類型實現了fmt.Stringer接口。
一個具體的類型可能實現了很多不相關的接口。考慮在一個組織出售數字文化産品比如音樂,電影和書籍的程序中可能定義了下列的具體類型:
``` go
```
Album
Book
Movie
@ -89,7 +107,9 @@ Podcast
TVEpisode
Track
```
我們可以把每個抽象的特點用接口來表示。一些特性對於所有的這些文化産品都是共通的,例如標題,創作日期和作者列表。
```go
type Artifact interface {
Title() string
@ -98,6 +118,7 @@ type Artifact interface {
}
```
其它的一些特性隻對特定類型的文化産品才有。和文字排版特性相關的隻有books和magazines還有隻有movies和TV劇集和屏幕分辨率相關。
```go
type Text interface {
Pages() int
@ -116,7 +137,9 @@ type Video interface {
Resolution() (x, y int)
}
```
這些接口不止是一種有用的方式來分組相關的具體類型和表示他們之間的共同特定。我們後面可能會發現其它的分組。舉例如果我們發現我們需要以同樣的方式處理Audio和Video我們可以定義一個Streamer接口來代表它們之間相同的部分而不必對已經存在的類型做改變。
```go
type Streamer interface {
Stream() (io.ReadCloser, error)
@ -124,4 +147,5 @@ type Streamer interface {
Format() string
}
```
每一個具體類型的組基於它們相同的行爲可以表示成一個接口類型。不像基於類的語言他們一個類實現的接口集合需要進行顯式的定義在Go語言中我們可以在需要的時候定義一個新的抽象或者特定特點的組而不需要脩改具體類型的定義。當具體的類型來自不同的作者時這種方式會特别有用。當然也確實沒有必要在具體的類型中指出這些共性。

View File

@ -193,6 +193,6 @@ func main() {
最後一個重要的提示就像我們在1.7節中提到的web服務器在一個新的協程中調用每一個handler所以當handler獲取其它協程或者這個handler本身的其它請求也可以訪問的變量時一定要使用預防措施比如鎖機製。我們後面的兩章中講到併發相關的知識。
**練習 7.11** 增加額外的handler讓客服端可以創建讀取更新和刪除數據庫記録。例如一個形如/update?item=socks&price=6的請求會更新庫存清單里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。註意這個脩改會引入變量同時更新的問題
**練習 7.11** 增加額外的handler讓客服端可以創建讀取更新和刪除數據庫記録。例如一個形如 `/update?item=socks&price=6` 的請求會更新庫存清單里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。(註意:這個脩改會引入變量同時更新的問題)
**練習 7.12** 脩改/list的handler讓它把輸出打印成一個HTML的表格而不是文本。html/template包(§4.6)可能會對你有幫助。

View File

@ -147,7 +147,7 @@ $ ./netcat1
$ killall clock2
```
練習8.1: 脩改clock2來支持傳入參數作爲端口號然後寫一個clockwall的程序這個程序可以同時與多個clock服務器通信從多服務器中讀取時間併且在一個表格中一次顯示所有服務傳迴的結果類似於你在某些辦公室里看到的時鐘牆。如果你有地理學上分布式的服務器可以用的話讓這些服務器跑在不同的機器上面或者在同一台機器上跑多個不同的實例這些實例監聽不同的端口假裝自己在不同的時區。像下面這樣
**練習 8.1** 脩改clock2來支持傳入參數作爲端口號然後寫一個clockwall的程序這個程序可以同時與多個clock服務器通信從多服務器中讀取時間併且在一個表格中一次顯示所有服務傳迴的結果類似於你在某些辦公室里看到的時鐘牆。如果你有地理學上分布式的服務器可以用的話讓這些服務器跑在不同的機器上面或者在同一台機器上跑多個不同的實例這些實例監聽不同的端口假裝自己在不同的時區。像下面這樣
```
$ TZ=US/Eastern ./clock2 -port 8010 &
@ -156,4 +156,4 @@ $ TZ=Europe/London ./clock2 -port 8030 &
$ clockwall NewYork=localhost:8010 Tokyo=localhost:8020 London=localhost:8030
```
練習8.2: 實現一個併發FTP服務器。服務器應該解析客戶端來的一些命令比如cd命令來切換目録ls來列出目録內文件get和send來傳輸文件close來關閉連接。你可以用標準的ftp命令來作爲客戶端或者也可以自己實現一個。
**練習 8.2** 實現一個併發FTP服務器。服務器應該解析客戶端來的一些命令比如cd命令來切換目録ls來列出目録內文件get和send來傳輸文件close來關閉連接。你可以用標準的ftp命令來作爲客戶端或者也可以自己實現一個。

View File

@ -184,6 +184,6 @@ sizes channel攜帶了每一個文件的大小到main goroutine在main gorout
![](../images/ch8-05.png)
練習8.4: 脩改reverb2服務器在每一個連接中使用sync.WaitGroup來計數活躍的echo goroutine。當計數減爲零時關閉TCP連接的寫入像練習8.3中一樣。驗證一下你的脩改版netcat3客戶端會一直等待所有的併發“喊叫”完成卽使是在標準輸入流已經關閉的情況下。
**練習 8.4** 脩改reverb2服務器在每一個連接中使用sync.WaitGroup來計數活躍的echo goroutine。當計數減爲零時關閉TCP連接的寫入像練習8.3中一樣。驗證一下你的脩改版netcat3客戶端會一直等待所有的併發“喊叫”完成卽使是在標準輸入流已經關閉的情況下。
練習8.5: 使用一個已有的CPU綁定的順序程序比如在3.3節中我們寫的Mandelbrot程序或者3.2節中的3-D surface計算程序併將他們的主循環改爲併發形式使用channel來進行通信。在多核計算機上這個程序得到了多少速度上的改進使用多少個goroutine是最合適的呢
**練習 8.5** 使用一個已有的CPU綁定的順序程序比如在3.3節中我們寫的Mandelbrot程序或者3.2節中的3-D surface計算程序併將他們的主循環改爲併發形式使用channel來進行通信。在多核計算機上這個程序得到了多少速度上的改進使用多少個goroutine是最合適的呢

View File

@ -159,11 +159,10 @@ seen這個map被限定在main goroutine中也就是説這個map隻能在main
crawl函數爬到的鏈接在一個專有的goroutine中被發送到worklist中來避免死鎖。爲了節省空間這個例子的終止問題我們先不進行詳細闡述了。
練習8.6: 爲併發爬蟲增加深度限製。也就是説如果用戶設置了depth=3那麽隻有從首頁跳轉三次以內能夠跳到的頁面才能被抓取到。
**練習 8.6** 爲併發爬蟲增加深度限製。也就是説如果用戶設置了depth=3那麽隻有從首頁跳轉三次以內能夠跳到的頁面才能被抓取到。
練習8.7: 完成一個併發程序來創建一個線上網站的本地鏡像,把該站點的所有可達的頁面都抓取到本地硬盤。爲了省事,我們這里可以隻取出現在該域下的所有頁面(比如golang.org結尾譯註外鏈的應該就不算了。)當然了,出現在頁面里的鏈接你也需要進行一些處理,使其能夠在你的鏡像站點上進行跳轉,而不是指向原始的鏈接。
**練習 8.7** 完成一個併發程序來創建一個線上網站的本地鏡像,把該站點的所有可達的頁面都抓取到本地硬盤。爲了省事,我們這里可以隻取出現在該域下的所有頁面(比如golang.org結尾譯註外鏈的應該就不算了。)當然了,出現在頁面里的鏈接你也需要進行一些處理,使其能夠在你的鏡像站點上進行跳轉,而不是指向原始的鏈接。
譯註:
拓展閲讀:
http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/
**譯註:**
拓展閲讀 [Handling 1 Million Requests per Minute with Go](http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/)。

View File

@ -131,4 +131,4 @@ channel的零值是nil。也許會讓你覺得比較奇怪nil的channel有時
這使得我們可以用nil來激活或者禁用case來達成處理其它輸入或輸出事件時超時和取消的邏輯。我們會在下一節中看到一個例子。
練習8.8: 使用select來改造8.3節中的echo服務器爲其增加超時這樣服務器可以在客戶端10秒中沒有任何喊話時自動斷開連接。
**練習 8.8** 使用select來改造8.3節中的echo服務器爲其增加超時這樣服務器可以在客戶端10秒中沒有任何喊話時自動斷開連接。

View File

@ -181,4 +181,4 @@ func dirents(dir string) []os.FileInfo {
這個版本比之前那個快了好幾倍,盡管其具體效率還是和你的運行環境,機器配置相關。
練習8.9: 編寫一個du工具每隔一段時間將root目録下的目録大小計算併顯示出來。
**練習 8.9** 編寫一個du工具每隔一段時間將root目録下的目録大小計算併顯示出來。

View File

@ -83,8 +83,6 @@ func dirents(dir string) []os.FileInfo {
現在當取消發生時所有後台的goroutine都會迅速停止併且主函數會返迴。當然當主函數返迴時一個程序會退出而我們又無法在主函數退出的時候確認其已經釋放了所有的資源(譯註:因爲程序都退出了,你的代碼都沒法執行了)。這里有一個方便的竅門我們可以一用取代掉直接從主函數返迴我們調用一個panic然後runtime會把每一個goroutine的棧dump下來。如果main goroutine是唯一一個剩下的goroutine的話他會清理掉自己的一切資源。但是如果還有其它的goroutine沒有退出他們可能沒辦法被正確地取消掉也有可能被取消但是取消操作會很花時間所以這里的一個調研還是很有必要的。我們用panic來獲取到足夠的信息來驗證我們上面的判斷看看最終到底是什麽樣的情況。
練習8.10: HTTP請求可能會因http.Request結構體中Cancel channel的關閉而取消。脩改8.6節中的web crawler來支持取消http請求。
**練習 8.10** HTTP請求可能會因http.Request結構體中Cancel channel的關閉而取消。脩改8.6節中的web crawler來支持取消http請求。提示http.Get併沒有提供方便地定製一個請求的方法。你可以用http.NewRequest來取而代之設置它的Cancel字段然後用http.DefaultClient.Do(req)來進行這個http請求。
提示: http.Get併沒有提供方便地定製一個請求的方法。你可以用http.NewRequest來取而代之設置它的Cancel字段然後用http.DefaultClient.Do(req)來進行這個http請求。
練習8.11:緊接着8.4.4中的mirroredQuery流程實現一個併發請求url的fetch的變種。當第一個請求返迴時直接取消其它的請求。
**練習 8.11** 緊接着8.4.4中的mirroredQuery流程實現一個併發請求url的fetch的變種。當第一個請求返迴時直接取消其它的請求。

View File

@ -114,7 +114,10 @@ You are 127.0.0.1:64216 127.0.0.1:64216 has arrived
當與n個客戶端保持聊天session時這個程序會有2n+2個併發的goroutine然而這個程序卻併不需要顯式的鎖(§9.2)。clients這個map被限製在了一個獨立的goroutine中broadcaster所以它不能被併發地訪問。多個goroutine共享的變量隻有這些channel和net.Conn的實例兩個東西都是併發安全的。我們會在下一章中更多地解決約束併發安全以及goroutine中共享變量的含義。
練習8.12: 使broadcaster能夠將arrival事件通知當前所有的客戶端。爲了達成這個目的你需要有一個客戶端的集合併且在entering和leaving的channel中記録客戶端的名字。
練習8.13: 使聊天服務器能夠斷開空閒的客戶端連接比如最近五分鐘之後沒有發送任何消息的那些客戶端。提示可以在其它goroutine中調用conn.Close()來解除Read調用就像input.Scanner()所做的那樣。
練習8.14: 脩改聊天服務器的網絡協議這樣每一個客戶端就可以在entering時可以提供它們的名字。將消息前綴由之前的網絡地址改爲這個名字。
練習8.15: 如果一個客戶端沒有及時地讀取數據可能會導致所有的客戶端被阻塞。脩改broadcaster來跳過一條消息而不是等待這個客戶端一直到其準備好寫。或者爲每一個客戶端的消息發出channel建立緩衝區這樣大部分的消息便不會被丟掉broadcaster應該用一個非阻塞的send向這個channel中發消息。
**練習 8.12** 使broadcaster能夠將arrival事件通知當前所有的客戶端。爲了達成這個目的你需要有一個客戶端的集合併且在entering和leaving的channel中記録客戶端的名字。
**練習 8.13** 使聊天服務器能夠斷開空閒的客戶端連接比如最近五分鐘之後沒有發送任何消息的那些客戶端。提示可以在其它goroutine中調用conn.Close()來解除Read調用就像input.Scanner()所做的那樣。
**練習 8.14** 脩改聊天服務器的網絡協議這樣每一個客戶端就可以在entering時可以提供它們的名字。將消息前綴由之前的網絡地址改爲這個名字。
**練習 8.15** 如果一個客戶端沒有及時地讀取數據可能會導致所有的客戶端被阻塞。脩改broadcaster來跳過一條消息而不是等待這個客戶端一直到其準備好寫。或者爲每一個客戶端的消息發出channel建立緩衝區這樣大部分的消息便不會被丟掉broadcaster應該用一個非阻塞的send向這個channel中發消息。

View File

@ -170,5 +170,5 @@ func icer(iced chan<- *Cake, cooked <-chan *Cake) {
第三種避免數據競爭的方法是允許很多goroutine去訪問變量但是在同一個時刻最多隻有一個goroutine在訪問。這種方式被稱爲“互斥”在下一節來討論這個主題。
練習 9.1: 給gopl.io/ch9/bank1程序添加一個Withdraw(amount int)取款函數。其返迴結果應該要表明事務是成功了還是因爲沒有足夠資金失敗了。這條消息會被發送給monitor的goroutine且消息需要包含取款的額度和一個新的channel這個新channel會被monitor goroutine來把boolean結果發迴給Withdraw。
**練習 9.1** 給gopl.io/ch9/bank1程序添加一個Withdraw(amount int)取款函數。其返迴結果應該要表明事務是成功了還是因爲沒有足夠資金失敗了。這條消息會被發送給monitor的goroutine且消息需要包含取款的額度和一個新的channel這個新channel會被monitor goroutine來把boolean結果發迴給Withdraw。