diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 59dfe78..a52adfc 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,27 +1,11 @@ -# 原文作者 +# 貢獻者 -## Alan A. A. Donovan - -![Alan.Donovan](./images/Alan.Donovan.png) - -**Alan A. A. Donovan** is a member of Google’s Go team in New York. He holds computer science degrees from Cambridge and MIT and has been programming in industry since 1996. Since 2005, he has worked at Google on infrastructure projects and was the co-designer of its proprietary build system, Blaze. He has built many libraries and tools for static analysis of Go programs, including oracle, godoc -analysis, eg, and gorename. - -## Brian W. Kernighan - -![Brian W. Kernighan](./images/Brian.W.Kernighan.png) - -**Brian W. Kernighan** is a professor in the Computer Science Department at Princeton University. He was a member of technical staff in the Computing Science Research Center at Bell Labs from 1969 until 2000, where he worked on languages and tools for Unix. He is the co-author of several books, including The C Programming Language, Second Edition (Prentice Hall, 1988), and The Practice of Programming (Addison-Wesley, 1999). - -------- - -# 中文譯者 - -中文譯者 | 章節 +譯者 | 章節 -------------------------------------- | ------------------------- -`chai2010 ` | 前言/第二章/第三章/第四章/第十章/第十一章/第十二章/第十三章 -`CrazySssst` | 第三章(0節)/第五章 -`foreversmart ` | 第七章(0節/1節/2節) -`Xargin ` | 第一章/第六章/第八章/第九章 +`chai2010 ` | 前言/第2~4章/第10~13章 +`CrazySssst` | 第5章 +`foreversmart ` | 第7章 +`Xargin ` | 第1章/第6章/第8~9章 # 譯文授權 diff --git a/Makefile b/Makefile index 8b28164..bc36739 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,9 @@ review: gitbook build go run zh2tw.go . .md$$ zh2tw +fixlink: + go run fixlinks.go . .md$$ + cover: composite cover_patch.png cover_bgd.png cover.jpg convert -resize 1800x2360! cover.jpg cover.jpg diff --git a/README.md b/README.md index 7db6803..e55b3b6 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,6 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本 - `make zh2tw` 或 `go run zh2tw.go . "\.md$" zh2tw`,轉繁體。 - `make tw2zh` 或 `go run zh2tw.go . "\.md$" tw2zh`,轉簡體。 -如果是使用簡體中文的用戶,可在執行上述命令前運行`make tw2zh`命令,將繁體中文轉換爲簡體中文。 - ### Markdown 格式預覽 - [SUMMARY-github.md](SUMMARY-github.md) diff --git a/SUMMARY-github.md b/SUMMARY-github.md index 3f47933..515d37e 100644 --- a/SUMMARY-github.md +++ b/SUMMARY-github.md @@ -167,4 +167,4 @@ * [13.3. 示例: 深度相等判斷](ch13/ch13-03.md) * [13.4. 通過cgo調用C代碼](ch13/ch13-04.md) * [13.5. 幾點忠告](ch13/ch13-05.md) -* [附録](CONTRIBUTORS.md) +* [附録](appendix/appendix.md) diff --git a/SUMMARY.md b/SUMMARY.md index 2380612..da71f78 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -122,4 +122,4 @@ * [示例: 深度相等判斷](ch13/ch13-03.md) * [通過cgo調用C代碼](ch13/ch13-04.md) * [幾點忠告](ch13/ch13-05.md) -* [附録](CONTRIBUTORS.md) +* [附録](appendix/appendix.md) diff --git a/appendix/appendix.md b/appendix/appendix.md new file mode 100644 index 0000000..c80369a --- /dev/null +++ b/appendix/appendix.md @@ -0,0 +1,26 @@ +## 附録:作者/譯者 + +### 英文作者 + +- **[Alan A. A. Donovan](https://github.com/adonovan)** is a member of [Google’s Go](https://golang.org/) team in New York. He holds computer science degrees from Cambridge and MIT and has been programming in industry since 1996. Since 2005, he has worked at Google on infrastructure projects and was the co-designer of its proprietary build system, [Blaze](http://bazel.io/). He has built many libraries and tools for static analysis of Go programs, including [oracle](https://godoc.org/golang.org/x/tools/oracle), [`godoc -analysis`](https://godoc.org/golang.org/x/tools/cmd/godoc), eg, and [gorename](https://godoc.org/golang.org/x/tools/cmd/gorename). +- **[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)** is a professor in the Computer Science Department at Princeton University. He was a member of technical staff in the Computing Science Research Center at [Bell Labs](http://www.cs.bell-labs.com/) from 1969 until 2000, where he worked on languages and tools for [Unix](http://doc.cat-v.org/unix/). He is the co-author of several books, including [The C Programming Language, Second Edition (Prentice Hall, 1988)](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html), and [The Practice of Programming (Addison-Wesley, 1999)](https://en.wikipedia.org/wiki/The_Practice_of_Programming). + +------- + +### 中文譯者 + +中文譯者 | 章節 +-------------------------------------- | ------------------------- +`chai2010 ` | 前言/第2~4章/第10~13章 +`CrazySssst` | 第5章 +`foreversmart ` | 第7章 +`Xargin ` | 第1章/第6章/第8~9章 + +------- + +## 譯文授權 + +除特别註明外, 本站內容均采用[知識共享-署名(CC-BY) 3.0協議](http://creativecommons.org/licenses/by/3.0/)授權, 代碼遵循[Go項目的BSD協議](http://golang.org/LICENSE)授權. + +Creative Commons License + diff --git a/ch0/ch0-01.md b/ch0/ch0-01.md index eb11c36..0df2fa6 100644 --- a/ch0/ch0-01.md +++ b/ch0/ch0-01.md @@ -8,12 +8,12 @@ Go語言有時候被描述爲“C類似語言”,或者是“21世紀的C語言”。Go從C語言繼承了相似的表達式語法、控製流結構、基礎數據類型、調用參數傳值、指針等很多思想,還有C語言一直所看中的編譯後機器碼的運行效率以及和現有操作繫統的無縫適配。 -但是在Go語言的家族樹中還有其它的祖先。其中一個有影響力的分支來自Niklaus Wirth所設計的Pascal語言。然後Modula-2語言激發了包的概念。然後Oberon語言 摒棄了模塊接口文件和模塊實現文件之間的區别。第二代的Oberon-2語言直接影響了包的導入和聲明的語法,還有Oberon語言的面向對象特性所提供的方法的聲明語法等。 +但是在Go語言的家族樹中還有其它的祖先。其中一個有影響力的分支來自[Niklaus Wirth](https://en.wikipedia.org/wiki/Niklaus_Wirth)所設計的[Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language))語言。然後[Modula-2](https://en.wikipedia.org/wiki/Modula-2)語言激發了包的概念。然後[Oberon](https://en.wikipedia.org/wiki/Oberon_(programming_language))語言摒棄了模塊接口文件和模塊實現文件之間的區别。第二代的[Oberon-2](https://en.wikipedia.org/wiki/Oberon-2_(programming_language))語言直接影響了包的導入和聲明的語法,還有[Oberon](https://en.wikipedia.org/wiki/Oberon_(programming_language))語言的面向對象特性所提供的方法的聲明語法等。 -Go語言的另一支祖先,帶來了Go語言區别其他語言的重要特性,靈感來自於貝爾實驗室的Tony Hoare於1978年發表的鮮爲外界所知的關於併發研究的基礎文獻 *順序通信進程* ( *communicating sequential processes* ,縮寫爲CSP)。在CSP中,程序是一組中間沒有共享狀態的平行運行的處理過程,它們之間使用管道進行通信和控製同步。不過Tony Hoare的CSP隻是一個用於描述併發性基本概念的描述語言,併不是一個可以編寫可執行程序的通用編程語言。 +Go語言的另一支祖先,帶來了Go語言區别其他語言的重要特性,靈感來自於貝爾實驗室的[Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)於1978年發表的鮮爲外界所知的關於併發研究的基礎文獻 *順序通信進程* ( *[communicating sequential processes](https://en.wikipedia.org/wiki/Communicating_sequential_processes)* ,縮寫爲[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes))。在[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)中,程序是一組中間沒有共享狀態的平行運行的處理過程,它們之間使用管道進行通信和控製同步。不過[Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)的[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)隻是一個用於描述併發性基本概念的描述語言,併不是一個可以編寫可執行程序的通用編程語言。 -接下來,Rob Pike和其他人開始不斷嚐試將CSP引入實際的編程語言中。他們第一次嚐試引入CSP特性的編程語言叫Squeak(老鼠間交流的語言),是一個提供鼠標和鍵盤事件處理的編程語言,它的管道是靜態創建的。然後是改進版的Newsqueak語言,提供了類似C語言語句和表達式的語法和類似Pascal語言的推導語法。Newsqueak是一個帶垃圾迴收的純函數式語言,它再次針對鍵盤、鼠標和窗口事件管理。但是在Newsqueak語言中管道是動態創建的,屬於第一類值, 可以保存到變量中。 +接下來,Rob Pike和其他人開始不斷嚐試將[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)引入實際的編程語言中。他們第一次嚐試引入[CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)特性的編程語言叫[Squeak](http://doc.cat-v.org/bell_labs/squeak/)(老鼠間交流的語言),是一個提供鼠標和鍵盤事件處理的編程語言,它的管道是靜態創建的。然後是改進版的[Newsqueak](http://doc.cat-v.org/bell_labs/squeak/)語言,提供了類似C語言語句和表達式的語法和類似[Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language))語言的推導語法。Newsqueak是一個帶垃圾迴收的純函數式語言,它再次針對鍵盤、鼠標和窗口事件管理。但是在Newsqueak語言中管道是動態創建的,屬於第一類值, 可以保存到變量中。 -在Plan9操作繫統中,這些優秀的想法被吸收到了一個叫Alef的編程語言中。Alef試圖將Newsqueak語言改造爲繫統編程語言,但是因爲缺少垃圾迴收機製而導致併發編程很痛苦。(譯註:在Aelf之後還有一個叫Limbo的編程語言,Go語言從其中借鑒了很多特性。 具體請參考Pike的講稿:http://talks.golang.org/2012/concurrency.slide#9 ) +在Plan9操作繫統中,這些優秀的想法被吸收到了一個叫[Alef](http://doc.cat-v.org/plan_9/2nd_edition/papers/alef/)的編程語言中。Alef試圖將Newsqueak語言改造爲繫統編程語言,但是因爲缺少垃圾迴收機製而導致併發編程很痛苦。(譯註:在Aelf之後還有一個叫[Limbo](http://doc.cat-v.org/inferno/4th_edition/limbo_language/)的編程語言,Go語言從其中借鑒了很多特性。 具體請參考Pike的講稿:http://talks.golang.org/2012/concurrency.slide#9 ) -Go語言的其他的一些特性零散地來自於其他一些編程語言;比如iota語法是從APL語言借鑒,詞法作用域與嵌套函數來自於Scheme語言(和其他很多語言)。當然,我們也可以從Go中發現很多創新的設計。比如Go語言的切片爲動態數組提供了有效的隨機存取的性能,這可能會讓人聯想到鏈表的底層的共享機製。還有Go語言新發明的defer語句。 +Go語言的其他的一些特性零散地來自於其他一些編程語言;比如iota語法是從[APL](https://en.wikipedia.org/wiki/APL_(programming_language))語言借鑒,詞法作用域與嵌套函數來自於[Scheme](https://en.wikipedia.org/wiki/Scheme_(programming_language))語言(和其他很多語言)。當然,我們也可以從Go中發現很多創新的設計。比如Go語言的切片爲動態數組提供了有效的隨機存取的性能,這可能會讓人聯想到鏈表的底層的共享機製。還有Go語言新發明的defer語句。 diff --git a/ch0/ch0-02.md b/ch0/ch0-02.md index 363e403..716710a 100644 --- a/ch0/ch0-02.md +++ b/ch0/ch0-02.md @@ -2,9 +2,9 @@ 所有的編程語言都反映了語言設計者對編程哲學的反思,通常包括之前的語言所暴露的一些不足地方的改進。Go項目是在Google公司維護超級複雜的幾個軟件繫統遇到的一些問題的反思(但是這類問題絶不是Google公司所特有的)。 -正如Rob Pike所説,“軟件的複雜性是乘法級相關的”,通過增加一個部分的複雜性來脩複問題通常將慢慢地增加其他部分的複雜性。通過增加功能和選項和配置是脩複問題的最快的途徑,但是這很容易讓人忘記簡潔的內涵,卽使從長遠來看,簡潔依然是好軟件的關鍵因素。 +正如[Rob Pike](http://genius.cat-v.org/rob-pike/)所説,“軟件的複雜性是乘法級相關的”,通過增加一個部分的複雜性來脩複問題通常將慢慢地增加其他部分的複雜性。通過增加功能和選項和配置是脩複問題的最快的途徑,但是這很容易讓人忘記簡潔的內涵,卽使從長遠來看,簡潔依然是好軟件的關鍵因素。 -簡潔的設計需要在工作開始的時候舍棄不必要的想法,併且在軟件的生命週期內嚴格區别好的改變或壞的改變。通過足夠的努力,一個好的改變可以在不破壞原有完整概念的前提下保持自適應,正如Fred Brooks所説的“概念完整性”;而一個壞的改變則不能達到這個效果,它們僅僅是通過膚淺的和簡單的妥協來破壞原有設計的一致性。隻有通過簡潔的設計,才能讓一個繫統保持穩定、安全和持續的進化。 +簡潔的設計需要在工作開始的時候舍棄不必要的想法,併且在軟件的生命週期內嚴格區别好的改變或壞的改變。通過足夠的努力,一個好的改變可以在不破壞原有完整概念的前提下保持自適應,正如[Fred Brooks](http://www.cs.unc.edu/~brooks/)所説的“概念完整性”;而一個壞的改變則不能達到這個效果,它們僅僅是通過膚淺的和簡單的妥協來破壞原有設計的一致性。隻有通過簡潔的設計,才能讓一個繫統保持穩定、安全和持續的進化。 Go項目包括編程語言本身,附帶了相關的工具和標準庫,最後但併非代表不重要的,關於簡潔編程哲學的宣言。就事後諸葛的角度來看,Go語言的這些地方都做的還不錯:擁有自動垃圾迴收、一個包繫統、函數作爲一等公民、詞法作用域、繫統調用接口、隻讀的UTF8字符串等。但是Go語言本身隻有很少的特性,也不太可能添加太多的特性。例如,它沒有隱式的數值轉換,沒有構造函數和析構函數,沒有運算符重載,沒有默認參數,也沒有繼承,沒有泛型,沒有異常,沒有宏,沒有函數脩飾,更沒有線程局部存儲。但是語言本身是成熟和穩定的,而且承諾保證向後兼容:用之前的Go語言編寫程序可以用新版本的Go語言編譯器和標準庫直接構建而不需要脩改代碼。 diff --git a/ch0/ch0-05.md b/ch0/ch0-05.md index 9fc7329..1498009 100644 --- a/ch0/ch0-05.md +++ b/ch0/ch0-05.md @@ -1,14 +1,14 @@ ## 致謝 -Rob Pike和Russ Cox,以及很多其他Go糰隊的覈心成員多次仔細閲讀了本書的手稿,他們對本書的組織結構和表述用詞等給出了很多寶貴的建議。在準備日文版翻譯的時候,Yoshiki Shibata更是仔細地審閲了本書的每個部分,及時發現了諸多英文和代碼的錯誤。我們非常感謝本書的每一位審閲者,併感謝對本書給出了重要的建議的Brian Goetz、Corey Kosak、Arnold Robbins、Josh Bleecher Snyder和Peter Weinberger等人。 +[Rob Pike](http://genius.cat-v.org/rob-pike/)和[Russ Cox](http://research.swtch.com/),以及很多其他Go糰隊的覈心成員多次仔細閲讀了本書的手稿,他們對本書的組織結構和表述用詞等給出了很多寶貴的建議。在準備日文版翻譯的時候,Yoshiki Shibata更是仔細地審閲了本書的每個部分,及時發現了諸多英文和代碼的錯誤。我們非常感謝本書的每一位審閲者,併感謝對本書給出了重要的建議的Brian Goetz、Corey Kosak、Arnold Robbins、Josh Bleecher Snyder和Peter Weinberger等人。 我們還感謝Sameer Ajmani、Ittai Balaban、David Crawshaw、Billy Donohue、Jonathan Feinberg、Andrew Gerrand、Robert Griesemer、John Linderman、Minux Ma(譯註:中国人,Go糰隊成員。)、Bryan Mills、Bala Natarajan、Cosmos Nicolaou、Paul Staniforth、Nigel Tao(譯註:好像是陶哲軒的兄弟)以及Howard Trickey給出的許多有價值的建議。我們還要感謝David Brailsford和Raph Levien關於類型設置的建議。 我們的來自Addison-Wesley的編輯Greg Doench收到了很多幫助,從最開始就得到了越來越多的幫助。來自AW生産糰隊的John Fuller、Dayna Isley、Julie Nahil、Chuti Prasertsith到Barbara Wood,感謝你們的熱心幫助。 -Alan Donovan特别感謝:Sameer Ajmani、Chris Demetriou、Walt Drummond和Google公司的Reid Tatge允許他有充裕的時間去寫本書;感謝Stephen Donovan的建議和始終如一的鼓勵,以及他的妻子Leila Kazemi併沒有讓他爲了家庭瑣事而分心,併熱情堅定地支持這個項目。 +[Alan Donovan](https://github.com/adonovan)特别感謝:Sameer Ajmani、Chris Demetriou、Walt Drummond和Google公司的Reid Tatge允許他有充裕的時間去寫本書;感謝Stephen Donovan的建議和始終如一的鼓勵,以及他的妻子Leila Kazemi併沒有讓他爲了家庭瑣事而分心,併熱情堅定地支持這個項目。 -Brian Kernighan特别感謝:朋友和同事對他的耐心和寬容,讓他慢慢地梳理本書的寫作思路。同時感謝他的妻子Meg和其他很多朋友對他寫作事業的支持。 +[Brian Kernighan](http://www.cs.princeton.edu/~bwk/)特别感謝:朋友和同事對他的耐心和寬容,讓他慢慢地梳理本書的寫作思路。同時感謝他的妻子Meg和其他很多朋友對他寫作事業的支持。 2015年 10月 於 紐約 diff --git a/ch1/ch1-01.md b/ch1/ch1-01.md index 9118ae5..739f5f8 100644 --- a/ch1/ch1-01.md +++ b/ch1/ch1-01.md @@ -1,6 +1,6 @@ ## 1.1. Hello, World -我們以1978年出版的C語言聖經《The C Programming Language》中經典的“hello world”案例來開始吧(譯註:本書作者之一Brian W. Kernighan也是C語言聖經一書的作者)。C語言對Go語言的設計産生了很多影響。用這個例子,我們來講解一些Go語言的覈心特性: +我們以1978年出版的C語言聖經[《The C Programming Language》](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)中經典的“hello world”案例來開始吧(譯註:本書作者之一Brian W. Kernighan也是C語言聖經一書的作者)。C語言對Go語言的設計産生了很多影響。用這個例子,我們來講解一些Go語言的覈心特性: ```go gopl.io/ch1/helloworld diff --git a/ch2/ch2-05.md b/ch2/ch2-05.md index 4e70384..d5f3dbf 100644 --- a/ch2/ch2-05.md +++ b/ch2/ch2-05.md @@ -12,7 +12,7 @@ type 類型名字 底層類型 類型聲明語句一般出現在包一級,因此如果新創建的類型名字的首字符大寫,則在外部包也可以使用。 -譯註:對於中文漢字,Unicode標誌都作爲小寫字母處理,因此中文的命名默認不能導出;不過国內的用戶針對該問題提出了我們自己的間接,根據RobPike的迴複,在Go2中有可能會將中日韓等字符當作大寫字母處理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的迴複: +譯註:對於中文漢字,Unicode標誌都作爲小寫字母處理,因此中文的命名默認不能導出;不過国內的用戶針對該問題提出了不同的看法,根據RobPike的迴複,在Go2中有可能會將中日韓等字符當作大寫字母處理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的迴複: > A solution that's been kicking around for a while: > diff --git a/ch5/ch5-03.md b/ch5/ch5-03.md index dbbac2c..5e44478 100644 --- a/ch5/ch5-03.md +++ b/ch5/ch5-03.md @@ -1,3 +1,117 @@ ## 5.3. 多返迴值 -TODO +在Go中,一個函數可以返迴多個值。我們已經在之前例子中看到,許多標準庫中的函數返迴2個值,一個是期望得到的返迴值,另一個是函數出錯時的錯誤信息。下面的例子會展示如何編寫多返迴值的函數。 + +下面的程序是findlinks的改進版本。脩改後的findlinks可以自己發起HTTP請求,這樣我們就不必再運行fetch。因爲HTTP請求和解析操作可能會失敗,因此findlinks聲明了2個返迴值:鏈接列表和錯誤信息。一般而言,HTML的解析器可以處理HTML頁面的錯誤結點,構造出HTML頁面結構,所以解析HTML很少失敗。這意味着如果findlinks函數失敗了,很可能是由於I/O的錯誤導致的。 + +```Go +gopl.io/ch5/findlinks2 +func main() { + for _, url := range os.Args[1:] { + links, err := findLinks(url) + if err != nil { + fmt.Fprintf(os.Stderr, "findlinks2: %v\n", err) + continue + } + for _, link := range links { + fmt.Println(link) + } + } +} + +// findLinks performs an HTTP GET request for url, parses the +// response as HTML, and extracts and returns the links. +func findLinks(url string) ([]string, error) { + resp, err := http.Get(url) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return nil, fmt.Errorf("getting %s: %s", url, resp.Status) + } + doc, err := html.Parse(resp.Body) + resp.Body.Close() + if err != nil { + return nil, fmt.Errorf("parsing %s as HTML: %v", url, err) + } + return visit(nil, doc), nil +} +``` + +在findlinks中,有4處return語句,每一處return都返迴了一組值。前三處return,將http和html包中的錯誤信息傳遞給findlinks的調用者。第一處return直接返迴錯誤信息,其他兩處通過fmt.Errorf(§7.8)輸出詳細的錯誤信息。如果findlinks成功結束,最後的return語句將一組解析獲得的連接返迴給用戶。 + +在finallinks中,我們必鬚確保resp.Body被關閉,釋放網絡資源。雖然Go的垃圾迴收機製會迴收不被使用的內存,但是這不包括操作繫統層面的資源,比如打開的文件、網絡連接。因此我們必鬚顯式的釋放這些資源。 + +調用多返迴值函數時,返迴給調用者的是一組值,調用者必鬚顯式的將這些值分配給變量: + +```Go +links, err := findLinks(url) +``` + +如果某個值不被使用,可以將其分配給blank identifier: + +```Go +links, _ := findLinks(url) // errors ignored +``` + +一個函數內部可以將另一個有多返迴值的函數作爲返迴值,下面的例子展示了與findLinks有相同功能的函數,兩者的區别在於下面的例子先輸出參數: + +```Go +func findLinksLog(url string) ([]string, error) { + log.Printf("findLinks %s", url) + return findLinks(url) +} +``` + +當你調用接受多參數的函數時,可以將一個返迴多參數的函數作爲該函數的參數。雖然這很少出現在實際生産代碼中,但這個特性在debug時很方便,我們隻需要一條語句就可以輸出所有的返迴值。下面的代碼是等價的: + +```Go +log.Println(findLinks(url)) +links, err := findLinks(url) +log.Println(links, err) +``` + +準確的變量名可以傳達函數返迴值的含義。尤其在返迴值的類型都相同時,就像下面這樣: + +```Go +func Size(rect image.Rectangle) (width, height int) +func Split(path string) (dir, file string) +func HourMinSec(t time.Time) (hour, minute, second int) +``` + +雖然良好的命名很重要,但你也不必爲每一個返迴值都取一個適當的名字。比如,按照慣例,函數的最後一個bool類型的返迴值表示函數是否運行成功,error類型的返迴值代表函數的錯誤信息,對於這些類似的慣例,我們不必思考合適的命名,它們都無需解釋。 + +如果一個函數將所有的返迴值都顯示的變量名,那麽該函數的return語句可以省略操作數。這稱之爲bare return。 + +```Go +// CountWordsAndImages does an HTTP GET request for the HTML +// document url and returns the number of words and images in it. +func CountWordsAndImages(url string) (words, images int, err error) { + resp, err := http.Get(url) + if err != nil { + return + } + doc, err := html.Parse(resp.Body) + resp.Body.Close() + if err != nil { + err = fmt.Errorf("parsing HTML: %s", err) + return + } + words, images = countWordsAndImages(doc) + return +} +func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ } +``` + +按照返迴值列表的次序,返迴所有的返迴值,在上面的例子中,每一個return語句等價於: + +```Go +return words, images, err +``` + +當一個函數有多處return語句以及許多返迴值時,bare return 可以減少代碼的重複,但是使得代碼難以被理解。舉個例子,如果你沒有仔細的審査代碼,很難發現前2處return等價於 return 0,0,err(Go會將返迴值 words和images在函數體的開始處,根據它們的類型,將其初始化爲0),最後一處return等價於 return words,image,nil。基於以上原因,不宜過度使用bare return。 + +**練習 5.5:** 實現countWordsAndImages。(參考練習4.9如何分詞) + +**練習 5.6:** 脩改gopl.io/ch3/surface (§3.2) 中的corner函數,將返迴值命名,併使用bare return。 diff --git a/ch5/ch5-04-2.md b/ch5/ch5-04-2.md index 08c12ba..a98fa3d 100644 --- a/ch5/ch5-04-2.md +++ b/ch5/ch5-04-2.md @@ -1,3 +1,3 @@ -### 5.4.2. 文件結尾(EOF) +### 5.4.2. 文件結尾錯誤(EOF) TODO diff --git a/ch5/ch5-04.md b/ch5/ch5-04.md index f66d07f..83cfb41 100644 --- a/ch5/ch5-04.md +++ b/ch5/ch5-04.md @@ -4,4 +4,4 @@ TODO {% include "./ch5-04-1.md" %} -{% include "./ch5-04-1.md" %} +{% include "./ch5-04-2.md" %} diff --git a/ch7/ch7-04.md b/ch7/ch7-04.md index 485bc37..fcd0c03 100644 --- a/ch7/ch7-04.md +++ b/ch7/ch7-04.md @@ -1,23 +1,29 @@ ## 7.4. flag.Value接口 + 在本章,我們會學到另一個標準的接口類型flag.Value是怎麽幫助命令行標記定義新的符號的。思考下面這個會休眠特定時間的程序: + ```go // gopl.io/ch7/sleep var period = flag.Duration("period", 1*time.Second, "sleep period") func main() { - flag.Parse() - fmt.Printf("Sleeping for %v...", *period) - time.Sleep(*period) - fmt.Println() + flag.Parse() + fmt.Printf("Sleeping for %v...", *period) + time.Sleep(*period) + fmt.Println() } ``` + 在它休眠前它會打印出休眠的時間週期。fmt包調用time.Duration的String方法打印這個時間週期是以用戶友好的註解方式,而不是一個納秒數字: + ``` $ go build gopl.io/ch7/sleep $ ./sleep Sleeping for 1s... ``` + 默認情況下,休眠週期是一秒,但是可以通過 -period 這個命令行標記來控製。flag.Duration函數創建一個time.Duration類型的標記變量併且允許用戶通過多種用戶友好的方式來設置這個變量的大小,這種方式還包括和String方法相同的符號排版形式。這種對稱設計使得用戶交互良好。 + ``` $ ./sleep -period 50ms Sleeping for 50ms... @@ -28,63 +34,73 @@ Sleeping for 1h30m0s... $ ./sleep -period "1 day" invalid value "1 day" for flag -period: time: invalid duration 1 day ``` + 因爲時間週期標記值非常的有用,所以這個特性被構建到了flag包中;但是我們爲我們自己的數據類型定義新的標記符號是簡單容易的。我們隻需要定義一個實現flag.Value接口的類型,如下: + ```go package flag // Value is the interface to the value stored in a flag. type Value interface { - String() string - Set(string) error + String() string + Set(string) error } ``` + String方法格式化標記的值用在命令行幫組消息中;這樣每一個flag.Value也是一個fmt.Stringer。Set方法解析它的字符串參數併且更新標記變量的值。實際上,Set方法和String是兩個相反的操作,所以最好的辦法就是對他們使用相同的註解方式。 讓我們定義一個允許通過攝氏度或者華氏溫度變換的形式指定溫度的celsiusFlag類型。註意celsiusFlag內嵌了一個Celsius類型(§2.5),因此不用實現本身就已經有String方法了。爲了實現flag.Value,我們隻需要定義Set方法: + ```go // gopl.io/ch7/tempconv // *celsiusFlag satisfies the flag.Value interface. type celsiusFlag struct{ Celsius } func (f *celsiusFlag) Set(s string) error { - var unit string - var value float64 - fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed - switch unit { - case "C", "°C": - f.Celsius = Celsius(value) - return nil - case "F", "°F": - f.Celsius = FToC(Fahrenheit(value)) - return nil - } - return fmt.Errorf("invalid temperature %q", s) + var unit string + var value float64 + fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed + switch unit { + case "C", "°C": + f.Celsius = Celsius(value) + return nil + case "F", "°F": + f.Celsius = FToC(Fahrenheit(value)) + return nil + } + return fmt.Errorf("invalid temperature %q", s) } ``` + 調用fmt.Sscanf函數從輸入s中解析一個浮點數(value)和一個字符串(unit)。雖然通常必鬚檢査Sscanf的錯誤返迴,但是在這個例子中我們不需要因爲如果有錯誤發生,就沒有switch case會匹配到。 下面的CelsiusFlag函數將所有邏輯都封裝在一起。它返迴一個內嵌在celsiusFlag變量f中的Celsius指針給調用者。Celsius字段是一個會通過Set方法在標記處理的過程中更新的變量。調用Var方法將標記加入應用的命令行標記集合中,有異常複雜命令行接口的全局變量flag.CommandLine.Programs可能有幾個這個類型的變量。調用Var方法將一個*celsiusFlag參數賦值給一個flag.Value參數,導致編譯器去檢査*celsiusFlag是否有必鬚的方法。 + ```go // CelsiusFlag defines a Celsius flag with the specified name, // default value, and usage, and returns the address of the flag variable. // The flag argument must have a quantity and a unit, e.g., "100C". func CelsiusFlag(name string, value Celsius, usage string) *Celsius { - f := celsiusFlag{value} - flag.CommandLine.Var(&f, name, usage) - return &f.Celsius + f := celsiusFlag{value} + flag.CommandLine.Var(&f, name, usage) + return &f.Celsius } ``` + 現在我們可以開始在我們的程序中使用新的標記: + ```go // gopl.io/ch7/tempflag var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature") func main() { - flag.Parse() - fmt.Println(*temp) + flag.Parse() + fmt.Println(*temp) } ``` + 下面是典型的場景: + ``` $ go build gopl.io/ch7/tempflag $ ./tempflag @@ -96,12 +112,14 @@ $ ./tempflag -temp 212°F $ ./tempflag -temp 273.15K invalid value "273.15K" for flag -temp: invalid temperature "273.15K" Usage of ./tempflag: - -temp value + -temp value the temperature (default 20°C) $ ./tempflag -help Usage of ./tempflag: - -temp value + -temp value the temperature (default 20°C) ``` -練習7.6:對tempFlag加入支持開爾文溫度。 -練習7.7:解釋爲什麽幫助信息在它的默認值是20.0沒有包含°C的情況下輸出了°C。 + +**練習 7.6:** 對tempFlag加入支持開爾文溫度。 + +**練習 7.7:** 解釋爲什麽幫助信息在它的默認值是20.0沒有包含°C的情況下輸出了°C。 diff --git a/ch8/ch8-04-1.md b/ch8/ch8-04-1.md index 7aeb3b2..9fdb573 100644 --- a/ch8/ch8-04-1.md +++ b/ch8/ch8-04-1.md @@ -1,10 +1,10 @@ ### 8.4.1. 不帶緩存的Channels -一個基於無緩存Channels的發送操作將導致發送者goroutine阻塞,直到另一個goroutine在相同的Channels上執行接收操作,當發送的值通過Channels成功傳輸之後,兩個goroutine可以繼續執行後面的語句。反之,如果接收操作先發送,那麽接收者goroutine也將阻塞,直到有另一個goroutine在相同的Channels上執行發送操作。 +一個基於無緩存Channels的發送操作將導致發送者goroutine阻塞,直到另一個goroutine在相同的Channels上執行接收操作,當發送的值通過Channels成功傳輸之後,兩個goroutine可以繼續執行後面的語句。反之,如果接收操作先發生,那麽接收者goroutine也將阻塞,直到有另一個goroutine在相同的Channels上執行發送操作。 基於無緩存Channels的發送和接收操作將導致兩個goroutine做一次同步操作。因爲這個原因,無緩存Channels有時候也被稱爲同步Channels。當通過一個無緩存Channels發送數據時,接收者收到數據發生在喚醒發送者goroutine之前(譯註:*happens before*,這是Go語言併發內存模型的一個關鍵術語!)。 -在討論併發編程時,當我們説x事件在y事件之前發生(*happens before*),我們併不是説x事件在時間上比y時間更早;我們要表達的意思是要保證在此之前事件都已經完成了,例如在此之前的更新某些變量的操作已經完成,你可以放心依賴這些保證了。 +在討論併發編程時,當我們説x事件在y事件之前發生(*happens before*),我們併不是説x事件在時間上比y時間更早;我們要表達的意思是要保證在此之前的事件都已經完成了,例如在此之前的更新某些變量的操作已經完成,你可以放心依賴這些已完成的事件了。 當我們説x事件旣不是在y事件之前發生也不是在y事件之後發生,我們就説x事件和y事件是併發的。這併不是意味着x事件和y事件就一定是同時發生的,我們隻是不能確定這兩個事件發生的先後順序。在下一章中我們將看到,當兩個goroutine併發訪問了相同的變量時,我們有必要保證某些事件的執行順序,以避免出現某些併發問題。 @@ -35,5 +35,5 @@ func main() { 基於channels發送消息有兩個重要方面。首先每個消息都有一個值,但是有時候通訊的事實和發生的時刻也同樣重要。當我們更希望強調通訊發生的時刻時,我們將它稱爲**消息事件**。有些消息事件併不攜帶額外的信息,它僅僅是用作兩個goroutine之間的同步,這時候我們可以用`struct{}`空結構體作爲channels元素的類型,雖然也可以使用bool或int類型實現同樣的功能,`done <- 1`語句也比`done <- struct{}{}`更短。 -**練習 8.3:** 在netcat3例子中,conn雖然是一個interface類型的值,但是其底層眞實類型是`*net.TCPConn`,代表一個TCP鏈接。一個TCP鏈接有讀和寫兩個部分,可以使用CloseRead和CloseWrite方法分别關閉它們。脩改netcat3的主goroutine代碼,隻關閉網絡鏈接中寫的部分,這樣的話後台goroutine可以在標準輸入被關閉後繼續打印從reverb1服務器傳迴的數據。(要在reverb2服務器也完成同樣的功能是比較睏難的;參考**練習 8.4:**) +**練習 8.3:** 在netcat3例子中,conn雖然是一個interface類型的值,但是其底層眞實類型是`*net.TCPConn`,代表一個TCP鏈接。一個TCP鏈接有讀和寫兩個部分,可以使用CloseRead和CloseWrite方法分别關閉它們。脩改netcat3的主goroutine代碼,隻關閉網絡鏈接中寫的部分,這樣的話後台goroutine可以在標準輸入被關閉後繼續打印從reverb1服務器傳迴的數據。(要在reverb2服務器也完成同樣的功能是比較睏難的;參考**練習 8.4**。) diff --git a/ch8/ch8-04-2.md b/ch8/ch8-04-2.md index 70c2585..371001d 100644 --- a/ch8/ch8-04-2.md +++ b/ch8/ch8-04-2.md @@ -1,3 +1,102 @@ -### 8.4.2. 管道(Pipeline) +### 8.4.2. 串聯的Channels(Pipeline) + +Channels也可以用於將多個goroutine鏈接在一起,一個Channels的輸出作爲下一個Channels的輸入。這種串聯的Channels就是所謂的管道(pipeline)。下面的程序用兩個channels將三個goroutine串聯起來,如圖8.1所示。 + +![](../images/ch8-01.png) + +第一個goroutine是一個計數器,用於生成0、1、2、……形式的整數序列,然後通過channel將該整數序列發送給第二個goroutine;第二個goroutine是一個求平方的程序,對收到的每個整數求平方,然後將平方後的結果通過第二個channel發送給第三個goroutine;第三個goroutine是一個打印程序,打印收到的每個整數。爲了保持例子清晰,我們有意選擇了非常簡單的函數,當然三個goroutine的計算很簡單,在現實中確實沒有必要爲如此簡單的運算構建三個goroutine。 + +```Go +gopl.io/ch8/pipeline1 + +func main() { + naturals := make(chan int) + squares := make(chan int) + + // Counter + go func() { + for x := 0; ; x++ { + naturals <- x + } + }() + + // Squarer + go func() { + for { + x := <-naturals + squares <- x * x + } + }() + + // Printer (in main goroutine) + for { + fmt.Println(<-squares) + } +} +``` + +如您所料,上面的程序將生成0、1、4、9、……形式的無窮數列。像這樣的串聯Channels的管道(Pipelines)可以用在需要長時間運行的服務中,每個長時間運行的goroutine可能會包含一個死循環,在不同goroutine的死循環內部使用串聯的Channels來通信。但是,如果我們希望通過Channels隻發送有限的數列該如何處理呢? + +如果發送者知道,沒有更多的值需要發送到channel的話,那麽讓接收者也能及時知道沒有多餘的值可接收將是有用的,因爲接收者可以停止不必要的接收等待。這可以通過內置的close函數來關閉channel實現: + +```Go +close(naturals) +``` + +當一個channel被關閉後,再向該channel發送數據將導致panic異常。當一個被關閉的channel中已經發送的數據都被成功接收後,後續的接收操作將不再阻塞,它們會立卽返迴一個零值。關閉上面例子中的naturals變量對應的channel併不能終止循環,它依然會收到一個永無休止的零值序列,然後將它們發送給打印者goroutine。 + +沒有辦法直接測試一個channel是否被關閉,但是接收操作有一個變體形式:它多接收一個結果,多接收的第二個結果是一個布爾值ok,ture表示成功從channels接收到值,false表示channels已經被關閉併且里面沒有值可接收。使用這個特性,我們可以脩改squarer函數中的循環代碼,當naturals對應的channel被關閉併沒有值可接收時跳出循環,併且也關閉squares對應的channel. + +```Go +// Squarer +go func() { + for { + x, ok := <-naturals + if !ok { + break // channel was closed and drained + } + squares <- x * x + } + close(squares) +}() +``` + +因爲上面的語法是笨拙的,而且這種處理模式很場景,因此Go語言的range循環可直接在channels上面迭代。使用range循環是上面處理模式的簡潔語法,它依次從channel接收數據,當channel被關閉併且沒有值可接收時跳出循環。 + +在下面的改進中,我們的計數器goroutine隻生成100個含數字的序列,然後關閉naturals對應的channel,這將導致計算平方數的squarer對應的goroutine可以正常終止循環併關閉squares對應的channel。(在一個更複雜的程序中,可以通過defer語句關閉對應的channel。)最後,主goroutine也可以正常終止循環併退出程序。 + +```Go +gopl.io/ch8/pipeline2 + +func main() { + naturals := make(chan int) + squares := make(chan int) + + // Counter + go func() { + for x := 0; x < 100; x++ { + naturals <- x + } + close(naturals) + }() + + // Squarer + go func() { + for x := range naturals { + squares <- x * x + } + close(squares) + }() + + // Printer (in main goroutine) + for x := range squares { + fmt.Println(x) + } +} +``` + +其實你併不需要關閉每一個channel。隻要當需要告訴接收者goroutine,所有的數據已經全部發送時才需要關閉channel。不管一個channel是否被關閉,當它沒有被引用時將會被Go語言的垃圾自動迴收器迴收。(不要將關閉一個打開文件的操作和關閉一個channel操作混淆。對於每個打開的文件,都需要在不使用的使用調用對應的Close方法來關閉文件。) + +視圖重複關閉一個channel將導致panic異常,視圖關閉一個nil值的channel也將導致panic異常。關閉一個channels還會觸發一個廣播機製,我們將在8.9節討論。 + -TODO diff --git a/ch8/ch8-04-3.md b/ch8/ch8-04-3.md index 48f2ad1..f2abc74 100644 --- a/ch8/ch8-04-3.md +++ b/ch8/ch8-04-3.md @@ -1,3 +1,54 @@ -### 8.4.3. 單向的Channels +### 8.4.3. 單方向的Channel + +隨着程序的增長,人們習慣於將大的函數拆分爲小的函數。我們前面的例子中使用了三個goroutine,然後用兩個channels連鏈接它們,它們都是main函數的局部變量。將三個goroutine拆分爲以下三個函數是自然的想法: + +```Go +func counter(out chan int) +func squarer(out, in chan int) +func printer(in chan int) +``` + +其中squarer計算平方的函數在兩個串聯Channels的中間,因此擁有兩個channels類型的參數,一個用於輸入一個用於輸出。每個channels都用有相同的類型,但是它們的使用方式想反:一個隻用於接收,另一個隻用於發送。參數的名字in和out已經明確表示了這個意圖,但是併無法保證squarer函數向一個in參數對應的channels發送數據或者從一個out參數對應的channels接收數據。 + +這種場景是典型的。當一個channel作爲一個函數參數是,它一般總是被專門用於隻發送或者隻接收。 + +爲了表明這種意圖併防止被濫用,Go語言的類型繫統提供了單方向的channel類型,分别用於隻發送或隻接收的channel。類型`chan<- int`表示一個隻發送int的channel,隻能發送不能接收。相反,類型`<-chan int`表示一個隻接收int的channel,隻能接收不能發送。(箭頭`<-`和關鍵字chan的相對位置表明了channel的方向。)這種限製將在編譯期檢測。 + +因爲關閉操作隻用於斷言不再向channel發送新的數據,所以隻有在發送者所在的goroutine才會調用close函數,因此對一個隻接收的channel調用close將是一個編譯錯誤。 + +這是改進的版本,這一次參數使用了單方向channel類型: + +```Go +gopl.io/ch8/pipeline3 +func counter(out chan<- int) { + for x := 0; x < 100; x++ { + out <- x + } + close(out) +} + +func squarer(out chan<- int, in <-chan int) { + for v := range in { + out <- v * v + } + close(out) +} + +func printer(in <-chan int) { + for v := range in { + fmt.Println(v) + } +} + +func main() { + naturals := make(chan int) + squares := make(chan int) + go counter(naturals) + go squarer(squares, naturals) + printer(squares) +} +``` + +調用counter(naturals)將導致將`chan int`類型的naturals隱式地轉換爲`chan<- int`類型隻發送型的channel。調用printer(squares)也會導致相似的隱式轉換,這一次是轉換爲`<-chan int`類型隻接收型的channel。任何雙向channel向單向channel變量的賦值操作都將導致該隱式轉換。這里併沒有反向轉換的語法:也就是不能一個將類似`chan<- int`類型的單向型的channel轉換爲`chan int`類型的雙向型的channel。 + -TODO diff --git a/ch8/ch8-04-4.md b/ch8/ch8-04-4.md index 4f4a1e2..be7f1e4 100644 --- a/ch8/ch8-04-4.md +++ b/ch8/ch8-04-4.md @@ -1,3 +1,85 @@ ### 8.4.4. 帶緩存的Channels -TODO +帶緩存的Channel內部持有一個元素隊列。隊列的最大容量是在調用make函數創建channel時通過第二個參數指定的。下面的語句創建了一個可以持有三個字符串元素的帶緩存Channel。圖8.2是ch變量對應的channel的圖形表示形式。 + + +```Go +ch = make(chan string, 3) +``` + +![](../images/ch8-02.png) + +向緩存Channel的發送操作就是向內部緩存隊列的尾部插入原因,接收操作則是從隊列的頭部刪除元素。如果內部緩存隊列是滿的,那麽發送操作將阻塞直到因另一個goroutine執行接收操作而釋放了新的隊列空間。相反,如果channel是空的,接收操作將阻塞直到有另一個goroutine執行發送操作而向隊列插入元素。 + +我們可以在無阻塞的情況下連續向新創建的channel發送三個值: + +```Go +ch <- "A" +ch <- "B" +ch <- "C" +``` + +此刻,channel的內部緩存隊列將是滿的(圖8.3),如果有第四個發送操作將發生阻塞。 + +![](../images/ch8-03.png) + +如果我們接收一個值, + +```Go +fmt.Println(<-ch) // "A" +``` + +那麽channel的緩存隊列將不是滿的也不是空的(圖8.4),因此對該channel執行的發送或接收操作都不會發送阻塞。通過這種方式,channel的緩存隊列解耦了接收和發送的goroutine。 + +![](../images/ch8-04.png) + +在某些特殊情況下,程序可能需要知道channel內部緩存的容量,可以用內置的cap函數獲取: + +```Go +fmt.Println(cap(ch)) // "3" +``` + +同樣,對於內置的len函數,如果傳入的是channel,那麽將返迴channel內部緩存隊列中有效元素的個數。因爲在併發程序中該信息會隨着接收操作而失效,但是它對某些故障診斷和性能優化會有幫助。 + +```Go +fmt.Println(len(ch)) // "2" +``` + +在繼續執行兩次接收操作後channel內部的緩存隊列將又成爲空的,如果有第四個接收操作將發生阻塞: + +```Go +fmt.Println(<-ch) // "B" +fmt.Println(<-ch) // "C" +``` + +在這個例子中,發送和接收操作都發生在同一個goroutine中,但是在眞是的程序中它們一般由不同的goroutine執行。Go語言新手有時候會將一個帶緩存的channel當作同一個goroutine中的隊列使用,雖然語法看似簡單,但實際上這是一個錯誤。Channel和goroutine的調度器機製是緊密相連的,一個發送操作——或許是整個程序——可能會永遠阻塞。如果你隻是需要一個簡單的隊列,使用slice就可以了。 + +下面的例子展示了一個使用了帶緩存channel的應用。它併發地向三個鏡像站點發出請求,三個鏡像站點分散在不同的地理位置。它們分别將收到的響應發送到帶緩存channel,最後接收者隻接收第一個收到的響應,也就是最快的那個響應。因此mirroredQuery函數可能在另外兩個響應慢的鏡像站點響應之前就返迴了結果。(順便説一下,多個goroutines併發地向同一個channel發送數據,或從同一個channel接收數據都是常見的用法。) + +```Go +func mirroredQuery() string { + responses := make(chan string, 3) + go func() { responses <- request("asia.gopl.io") }() + go func() { responses <- request("europe.gopl.io") }() + go func() { responses <- request("americas.gopl.io") }() + return <-responses // return the quickest response +} + +func request(hostname string) (response string) { /* ... */ } +``` + +如果我們使用了無緩存的channel,那麽兩個慢的goroutines將會因爲沒有人接收而被永遠卡住。這種情況,稱爲goroutines洩漏,這將是一個BUG。和垃圾變量不同,洩漏的goroutines併不會被自動迴收,因此確保每個不再需要的goroutine能正常退出是重要的。 + +關於無緩存或帶緩存channels之間的選擇,或者是帶緩存channels的容量大小的選擇,都可能影響程序的正確性。無緩存channel更強地保證了每個發送操作與相應的同步接收操作;但是對於帶緩存channel,這些操作是解耦的。同樣,卽使我們知道將要發送到一個channel的信息的數量上限,創建一個對應容量大小帶緩存channel也是不現實的,因爲這要求在執行任何接收操作之前緩存所有已經發送的值。如果未能分配足夠的緩衝將導致程序死鎖。 + +Channel的緩存也可能影響程序的性能。想象一家蛋糕店有三個廚師,一個烘焙,一個上醣衣,還有一個將每個蛋糕傳遞到它下一個廚師在生産線。在狹小的廚房空間環境,每個廚師在完成蛋糕後必鬚等待下一個廚師已經準備好接受它;這類似於在一個無緩存的channel上進行溝通。 + +如果在每個廚師之間有一個放置一個蛋糕的額外空間,那麽每個廚師就可以將一個完成的蛋糕臨時放在那里而馬上進入下一個蛋糕在製作中;這類似於將channel的緩存隊列的容量設置爲1。隻要每個廚師的平均工作效率相近,那麽其中大部分的傳輸工作將是迅速的,個體之間細小的效率差異將在交接過程中瀰補。如果廚師之間有更大的額外空間——也是就更大容量的緩存隊列——將可以在不停止生産線的前提下消除更大的效率波動,例如一個廚師可以短暫地休息,然後在加快趕上進度而不影響其其他人。 + +另一方面,如果生産線的前期階段一直快於後續階段,那麽它們之間的緩存在大部分時間都將是滿的。相反,如果後續階段比前期階段更快,那麽它們之間的緩存在大部分時間都將是空的。對於這類場景,額外的緩存併沒有帶來任何好處。 + +生産線的隱喻對於理解channels和goroutines的工作機製是很有幫助的。例如,如果第二階段是需要精心製作的複雜操作,一個廚師可能無法跟上第一個廚師的進度,或者是無法滿足第階段廚師的需求。要解決這個問題,我們可以雇傭另一個廚師來幫助完成第二階段的工作,他執行相同的任務但是獨立工作。這類似於基於相同的channels創建另一個獨立的goroutine。 + +我們沒有太多的空間展示全部細節,但是gopl.io/ch8/cake包模擬了這個蛋糕店,可以通過不同的參數調整。它還對上面提到的幾種場景提供對應的基準測試(§11.4) 。 + + diff --git a/ch9/ch9-06.md b/ch9/ch9-06.md index ad3d5a0..4a6ef8a 100644 --- a/ch9/ch9-06.md +++ b/ch9/ch9-06.md @@ -1,3 +1,11 @@ ## 9.6. 競爭條件檢測 -TODO +卽使我們小心到不能再小心,但在併發程序中犯錯還是太容易了。幸運的是,Go的runtime和工具鏈爲我們裝備了一個複雜但好用的動態分析工具,競爭檢査器(the race detector)。 + +隻要在go build,go run或者go test命令後面加上-race的flag,就會使編譯器創建一個你的應用的“脩改”版或者一個附帶了能夠記録所有運行期對共享變量訪問工具的test,併且會記録下每一個讀或者寫共享變量的goroutine的身份信息。另外,脩改版的程序會記録下所有的同步事件,比如go語句,channel操作,以及對(\*sync.Mutex).Lock,(\*sync.WaitGroup).Wait等等的調用。(完整的同步事件集合是在The Go Memory Model文檔中有説明,該文檔是和語言文檔放在一起的。譯註:https://golang.org/ref/mem) + +競爭檢査器會檢査這些事件,會尋找在哪一個goroutine中出現了這樣的case,例如其讀或者寫了一個共享變量,這個共享變量是被另一個goroutine在沒有進行榦預同步操作便直接寫入的。這種情況也就表明了是對一個共享變量的併發訪問,卽數據競爭。這個工具會打印一份報告,內容包含變量身份,讀取和寫入的goroutine中活躍的函數的調用棧。這些信息在定位問題時通常很有用。9.7節中會有一個競爭檢査器的實戰樣例。 + +競爭檢査器會報告所有的已經發生的數據競爭。然而,它隻能檢測到運行時的競爭條件;併不能證明之後不會發生數據競爭。所以爲了使結果盡量正確,請保證你的測試併發地覆蓋到了你到包。 + +由於需要額外的記録,因此構建時加了競爭檢測的程序跑起來會慢一些,且需要更大的內存,卽時是這樣,這些代價對於很多生産環境的工作來説還是可以接受的。對於一些偶發的競爭條件來説,讓競爭檢査器來榦活可以節省無數日夜的debugging。(譯註:多少服務端C和C艹程序員爲此盡摺腰) diff --git a/fixlinks.go b/fixlinks.go new file mode 100644 index 0000000..24aa701 --- /dev/null +++ b/fixlinks.go @@ -0,0 +1,186 @@ +// Copyright 2015 . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ingore + +// +// 给特殊模式标定的单词增加链接. +// +// Example: +// fixlinks +// fixlinks dir "\.go$" +// +// Help: +// fixlinks -h +// +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "unicode/utf8" +) + +const MaxFileSize = 8 << 20 // 8MB + +const usage = ` +Usage: fixlinks dir [nameFilter] + fixlinks -h + +Example: + fixlinks + fixlinks dir "\.go$" + +Report bugs to . +` + +func main() { + if len(os.Args) < 2 || os.Args[1] == "-h" { + fmt.Fprintln(os.Stderr, usage[1:len(usage)-1]) + os.Exit(0) + } + dir, nameFilter := os.Args[1], ".*" + if len(os.Args) > 2 { + nameFilter = os.Args[2] + } + + total := 0 + filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Fatal("filepath.Walk: ", err) + return err + } + if info.IsDir() { + return nil + } + relpath, err := filepath.Rel(dir, path) + if err != nil { + log.Fatal("filepath.Rel: ", err) + return err + } + mathed, err := regexp.MatchString(nameFilter, relpath) + if err != nil { + log.Fatal("regexp.MatchString: ", err) + } + if mathed { + if changed := convertFile(path); changed { + fmt.Printf("%s\n", relpath) + total++ + } + } + return nil + }) + fmt.Printf("total %d\n", total) +} + +func convertFile(path string) (changed bool) { + abspath, err := filepath.Abs(path) + if err != nil { + log.Fatal("convertFile: filepath.Abs:", err) + } + + fi, err := os.Lstat(abspath) + if err != nil { + log.Fatal("convertFile: os.Lstat:", err) + } + if fi.Size() > MaxFileSize { + return false + } + + oldData, err := ioutil.ReadFile(abspath) + if err != nil { + log.Fatal("convertFile: ioutil.ReadFile:", err) + } + if !utf8.Valid(oldData) { + return false + } + + newData := append([]byte{}, oldData...) + for re, v := range _RegexpLinksTable { + newData = re.ReplaceAll(newData, []byte(v)) + } + + if string(newData) == string(oldData) { + return false + } + + err = ioutil.WriteFile(abspath, newData, 0666) + if err != nil { + log.Fatal("convertFile: ioutil.WriteFile:", err) + } + return true +} + +var _RegexpLinksTable = func() map[*regexp.Regexp]string { + const ( + reHttp = `(https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|])?` + reWikiTail = `(\([-A-Za-z0-9+~_]+\))?` + ) + + m := make(map[*regexp.Regexp]string) + for k, v := range _LinkTable { + reKey := regexp.MustCompile(regexp.QuoteMeta(`[`+k+`]`) + `\(` + reHttp + reWikiTail + `\)`) + m[reKey] = fmt.Sprintf("[%s](%s)", k, v) + } + return m +}() + +var _LinkTable = map[string]string{ + + // 人名 + "Alan Donovan": "https://github.com/adonovan", + "Brian Kernighan": "http://www.cs.princeton.edu/~bwk/", + "Alan A. A. Donovan": "https://github.com/adonovan", + "Brian W. Kernighan": "http://www.cs.princeton.edu/~bwk/", + "Robert Griesemer": "http://research.google.com/pubs/author96.html", + "Rob Pike": "http://genius.cat-v.org/rob-pike/", + "Ken Thompson": "http://genius.cat-v.org/ken-thompson/", + "Russ Cox": "http://research.swtch.com/", + "Niklaus Wirth": "https://en.wikipedia.org/wiki/Niklaus_Wirth", + "Tony Hoare": "https://en.wikipedia.org/wiki/Tony_Hoare", + "Fred Brooks": "http://www.cs.unc.edu/~brooks/", + + // 图书 + "The C Programming Language": "http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html", + "The Practice of Programming": "https://en.wikipedia.org/wiki/The_Practice_of_Programming", + + // Go语言 + "Go": "https://golang.org/", + "Google’s Go": "https://golang.org/", + "oracle": "https://godoc.org/golang.org/x/tools/oracle", + "godoc -analysis": "https://godoc.org/golang.org/x/tools/cmd/godoc", + "gorename": "https://godoc.org/golang.org/x/tools/cmd/gorename", + + // 其他语言 + "Alef": "http://doc.cat-v.org/plan_9/2nd_edition/papers/alef/", + "APL": "https://en.wikipedia.org/wiki/APL_(programming_language)", + "Limbo": "http://doc.cat-v.org/inferno/4th_edition/limbo_language/", + "Modula-2": "https://en.wikipedia.org/wiki/Modula-2", + "Newsqueak": "http://doc.cat-v.org/bell_labs/squeak/", + "Oberon": "https://en.wikipedia.org/wiki/Oberon_(programming_language)", + "Oberon-2": "https://en.wikipedia.org/wiki/Oberon-2_(programming_language)", + "Pascal": "https://en.wikipedia.org/wiki/Pascal_(programming_language)", + "Scheme": "https://en.wikipedia.org/wiki/Scheme_(programming_language)", + "Squeak": "http://doc.cat-v.org/bell_labs/squeak/", + + // 系统 + "Unix": "http://doc.cat-v.org/unix/", + "UNIX": "http://doc.cat-v.org/unix/", + "Linux": "http://www.linux.org/", + "FreeBSD": "https://www.freebsd.org/", + "OpenBSD": "http://www.openbsd.org/", + "Mac OSX": "http://www.apple.com/cn/osx/", + "Mac OS X": "http://www.apple.com/cn/osx/", + "Plan9": "http://plan9.bell-labs.com/plan9/", + "Microsoft Windows": "https://www.microsoft.com/zh-cn/windows/", + + // 其他 + "Bell Labs": "http://www.cs.bell-labs.com/", + "communicating sequential processes": "https://en.wikipedia.org/wiki/Communicating_sequential_processes", + "CSP": "https://en.wikipedia.org/wiki/Communicating_sequential_processes", +} diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000..d287722 Binary files /dev/null and b/images/favicon.ico differ diff --git a/preface.md b/preface.md index f978c98..77880f4 100644 --- a/preface.md +++ b/preface.md @@ -23,7 +23,7 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本 *“Go是一個開源的編程語言,它很容易用於構建簡單、可靠和高效的軟件。”(摘自Go語言官方網站:http://golang.org )* -Go語言由來自Google公司的Robert Griesemer,Rob Pike和Ken Thompson三位大牛於2007年9月開始設計和實現,然後於2009年的11月對外正式發布(譯註:關於Go語言的創世紀過程請參考 http://talks.golang.org/2015/how-go-was-made.slide )。語言及其配套工具的設計目標是具有表達力,高效的編譯和執行效率,有效地編寫高效和健壯的程序。 +Go語言由來自Google公司的[Robert Griesemer](http://research.google.com/pubs/author96.html),[Rob Pike](http://genius.cat-v.org/rob-pike/)和[Ken Thompson](http://genius.cat-v.org/ken-thompson/)三位大牛於2007年9月開始設計和實現,然後於2009年的11月對外正式發布(譯註:關於Go語言的創世紀過程請參考 http://talks.golang.org/2015/how-go-was-made.slide )。語言及其配套工具的設計目標是具有表達力,高效的編譯和執行效率,有效地編寫高效和健壯的程序。 Go語言有着和C語言類似的語法外表,和C語言一樣是專業程序員的必備工具,可以用最小的代價獲得最大的戰果。 但是它不僅僅是一個更新的C語言。它還從其他語言借鑒了很多好的想法,同時避免引入過度的複雜性。 @@ -36,8 +36,8 @@ Go語言尤其適合編寫網絡服務相關基礎設施,同時也適合開發 因爲Go編寫的程序通常比腳本語言運行的更快也更安全,而且很少會發生意外的類型錯誤。 Go語言還是一個開源的項目,可以免費獲編譯器、庫、配套工具的源代碼。 -Go語言的貢獻者來自一個活躍的全球社區。Go語言可以運行在類UNIX繫統—— -比如Linux、FreeBSD、OpenBSD、Mac OSX——和Plan9繫統和Microsoft Windows操作繫統之上。 +Go語言的貢獻者來自一個活躍的全球社區。Go語言可以運行在類[UNIX](http://doc.cat-v.org/unix/)繫統—— +比如[Linux](http://www.linux.org/)、[FreeBSD](https://www.freebsd.org/)、[OpenBSD](http://www.openbsd.org/)、[Mac OSX](http://www.apple.com/cn/osx/)——和[Plan9](http://plan9.bell-labs.com/plan9/)繫統和[Microsoft Windows](https://www.microsoft.com/zh-cn/windows/)操作繫統之上。 Go語言編寫的程序無需脩改就可以運行在上面這些環境。 本書是爲了幫助你開始以有效的方式使用Go語言,充分利用語言本身的特性和自帶的標準庫去編寫清晰地道的Go程序。 diff --git a/progress.md b/progress.md index 6567341..15c1fd9 100644 --- a/progress.md +++ b/progress.md @@ -35,7 +35,7 @@ - [x] Chapter 5: Functions - [x] 5.1 Function Declarations - [x] 5.2 Recursion - - [ ] 5.3 Multiple Return Values + - [x] 5.3 Multiple Return Values - [ ] 5.4 Errors - [ ] 5.5 Function Values - [ ] 5.6 Anonymous Functions @@ -54,7 +54,7 @@ - [x] 7.1 Interfaces as Contracts - [x] 7.2 Interface Types - [x] 7.3 Interface Satisfaction - - [ ] 7.4 Parsing Flags with flag.Value + - [x] 7.4 Parsing Flags with flag.Value - [ ] 7.5 Interface Values - [ ] 7.6 Sorting with sort.Interface - [ ] 7.7 The http.Handler Interface @@ -70,7 +70,7 @@ - [x] 8.1 Goroutines - [x] 8.2 Example: Concurrent Clock Server - [x] 8.3 Example: Concu rent Echo Server - - [ ] 8.4 Channels + - [x] 8.4 Channels - [x] 8.5 Looping in Parallel - [x] 8.6 Example: Concurrent Web Crawler - [x] 8.7 Multiplexing with select @@ -83,7 +83,7 @@ - [x] 9.3 Read/Write Mutexes: sync.RWMutex - [ ] 9.4 Memory Synchronization - [ ] 9.5 Lazy Initialization: sync.Once - - [ ] 9.6 The Race Detector + - [x] 9.6 The Race Detector - [ ] 9.7 Example: Concurrent Non-Blocking Cache - [ ] 9.8 Goroutines and Threads - [x] Chapter 10: Packages and the Go Tool diff --git a/tools/cpdir.go b/tools/cpdir.go new file mode 100644 index 0000000..8d9a537 --- /dev/null +++ b/tools/cpdir.go @@ -0,0 +1,102 @@ +// Copyright 2013 . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ingore + +// +// Copy dir, support regexp. +// +// Example: +// cpdir src dst +// cpdir src dst "\.go$" +// cpdir src dst "\.tiff?$" +// cpdir src dst "\.tiff?|jpg|jpeg$" +// +// Help: +// cpdir -h +// +package main + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" +) + +const usage = ` +Usage: cpdir src dst [filter] + cpdir -h + +Example: + cpdir src dst + cpdir src dst "\.go$" + cpdir src dst "\.tiff?$" + cpdir src dst "\.tiff?|jpg|jpeg$" + +Report bugs to . +` + +func main() { + if len(os.Args) < 3 { + fmt.Fprintln(os.Stderr, usage[1:len(usage)-1]) + os.Exit(0) + } + filter := ".*" + if len(os.Args) > 3 { + filter = os.Args[3] + } + total := cpDir(os.Args[2], os.Args[1], filter) + fmt.Printf("total %d\n", total) +} + +func cpDir(dst, src, filter string) (total int) { + entryList, err := ioutil.ReadDir(src) + if err != nil && !os.IsExist(err) { + log.Fatal("cpDir: ", err) + } + for _, entry := range entryList { + if entry.IsDir() { + cpDir(dst+"/"+entry.Name(), src+"/"+entry.Name(), filter) + } else { + mathed, err := regexp.MatchString(filter, entry.Name()) + if err != nil { + log.Fatal("regexp.MatchString: ", err) + } + if mathed { + srcFname := filepath.Clean(src + "/" + entry.Name()) + dstFname := filepath.Clean(dst + "/" + entry.Name()) + fmt.Printf("copy %s\n", srcFname) + + cpFile(dstFname, srcFname) + total++ + } + } + } + return +} + +func cpFile(dst, src string) { + err := os.MkdirAll(filepath.Dir(dst), 0666) + if err != nil && !os.IsExist(err) { + log.Fatal("cpFile: ", err) + } + fsrc, err := os.Open(src) + if err != nil { + log.Fatal("cpFile: ", err) + } + defer fsrc.Close() + + fdst, err := os.Create(dst) + if err != nil { + log.Fatal("cpFile: ", err) + } + defer fdst.Close() + if _, err = io.Copy(fdst, fsrc); err != nil { + log.Fatal("cpFile: ", err) + } +} diff --git a/tools/lookpath.go b/tools/lookpath.go new file mode 100644 index 0000000..b8a505c --- /dev/null +++ b/tools/lookpath.go @@ -0,0 +1,44 @@ +// Copyright 2014 . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ingore + +// Lookpath is a simple which. +package main + +import ( + "fmt" + "os" + "os/exec" + "strings" +) + +func main() { + if len(os.Args) < 2 { + fmt.Printf(`Usage: lookpath COMMAND [...] +Write the full path of COMMAND(s) to standard output. + +Report bugs to . +`) + os.Exit(0) + } + for i := 1; i < len(os.Args); i++ { + path, err := exec.LookPath(os.Args[i]) + if err != nil { + fmt.Printf("lookpath: no %s in (%v)\n", os.Args[i], GetEnv("PATH")) + os.Exit(0) + } + fmt.Println(path) + } +} + +func GetEnv(key string) string { + key = strings.ToUpper(key) + "=" + for _, env := range os.Environ() { + if strings.HasPrefix(strings.ToUpper(env), key) { + return env[len(key):] + } + } + return "" +} diff --git a/tools/lsdir.go b/tools/lsdir.go new file mode 100644 index 0000000..9617b66 --- /dev/null +++ b/tools/lsdir.go @@ -0,0 +1,97 @@ +// Copyright 2013 . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ingore + +// +// List files, support file/header regexp. +// +// Example: +// lsdir dir +// lsdir dir "\.go$" +// lsdir dir "\.go$" "chaishushan" +// lsdir dir "\.tiff?|jpg|jpeg$" +// +// Help: +// lsdir -h +// +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" +) + +const usage = ` +Usage: lsdir dir [nameFilter [dataFilter]] + lsdir -h + +Example: + lsdir dir + lsdir dir "\.go$" + lsdir dir "\.go$" "chaishushan" + lsdir dir "\.tiff?|jpg|jpeg$" + +Report bugs to . +` + +func main() { + if len(os.Args) < 2 || os.Args[1] == "-h" { + fmt.Fprintln(os.Stderr, usage[1:len(usage)-1]) + os.Exit(0) + } + dir, nameFilter, dataFilter := os.Args[1], ".*", "" + if len(os.Args) > 2 { + nameFilter = os.Args[2] + } + if len(os.Args) > 3 { + dataFilter = os.Args[3] + } + + total := 0 + filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Fatal("filepath.Walk: ", err) + return err + } + if info.IsDir() { + return nil + } + relpath, err := filepath.Rel(dir, path) + if err != nil { + log.Fatal("filepath.Rel: ", err) + return err + } + mathed, err := regexp.MatchString(nameFilter, relpath) + if err != nil { + log.Fatal("regexp.MatchString: ", err) + } + if mathed { + if dataFilter != "" { + data, err := ioutil.ReadFile(path) + if err != nil { + fmt.Printf("ioutil.ReadFile: %s\n", path) + log.Fatal("ioutil.ReadFile: ", err) + } + mathed, err := regexp.MatchString(dataFilter, string(data)) + if err != nil { + log.Fatal("regexp.MatchString: ", err) + } + if mathed { + fmt.Printf("%s\n", relpath) + total++ + } + } else { + fmt.Printf("%s\n", relpath) + total++ + } + } + return nil + }) + fmt.Printf("total %d\n", total) +} diff --git a/tools/md5.go b/tools/md5.go new file mode 100644 index 0000000..f60c8fb --- /dev/null +++ b/tools/md5.go @@ -0,0 +1,121 @@ +// Copyright 2013 . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ingore + +// +// Cacl dir or file MD5Sum, support regexp. +// +// Example: +// md5 file +// md5 dir "\.go$" +// md5 dir "\.tiff?$" +// md5 dir "\.tiff?|jpg|jpeg$" +// +// Help: +// cpdir -h +// +package main + +import ( + "crypto/md5" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "sort" +) + +const usage = ` +Usage: md5 [dir|file [filter]] + md5 -h + +Example: + md5 file + md5 dir "\.go$" + md5 dir "\.go$" + md5 dir "\.tiff?$" + md5 dir "\.tiff?|jpg|jpeg$" + +Report bugs to . +` + +func main() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, usage[1:len(usage)-1]) + os.Exit(0) + } + filter := ".*" + if len(os.Args) > 2 { + filter = os.Args[2] + } + + if name := os.Args[1]; IsDir(name) { + m, err := MD5Dir(name, filter) + if err != nil { + log.Fatalf("%s: %v", name, err) + } + var paths []string + for path := range m { + paths = append(paths, path) + } + sort.Strings(paths) + for _, path := range paths { + fmt.Printf("%x *%s\n", m[path], path) + } + } else { + sum, err := MD5File(name) + if err != nil { + log.Fatalf("%s: %v", name, err) + } + fmt.Printf("%x *%s\n", sum, name) + } +} + +func IsDir(name string) bool { + fi, err := os.Lstat(name) + if err != nil { + log.Fatal(err) + } + return fi.IsDir() +} + +func MD5Dir(root string, filter string) (map[string][md5.Size]byte, error) { + m := make(map[string][md5.Size]byte) + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + mathed, err := regexp.MatchString(filter, path) + if err != nil { + log.Fatal("regexp.MatchString: ", err) + } + if mathed { + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + m[path] = md5.Sum(data) + } + return nil + }) + if err != nil { + return nil, err + } + return m, nil +} + +func MD5File(filename string) (sum [md5.Size]byte, err error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return + } + sum = md5.Sum(data) + return +}