go語言入門

Go語言最主要的特性:
 自動垃圾回收
 更豐富的內置類型
 函數多返回值
 錯誤處理
 匿名函數和閉包
 類型和接口
 併發編程
 反射
 語言交互性

1.2.4 錯誤處理
Go語言引入了3個關鍵字用於標準的錯誤處理流程,這3個關鍵字分別爲defer、panic和
recover。


1:編譯環境準備 在Go 1發佈以前,開發者要想使用Go,只能自行下載代碼並進行編譯,而如今能夠直接下 載對應的安裝包進行安裝,安裝包的下載地址爲http://code.google.com/p/go/downloads/list。 在*nix環境中,Go默認會被安裝到/usr/local/go目錄中。安裝包在安裝完成後會自動添加執行 文件目錄到系統路徑中。

Ubuntu安裝Go:
sudo add-apt-repository ppa:gophers/go
sudo apt-get update
sudo apt-get install golang-stablehtml

linux

sudo apt-get install golang
程序員

或者直接下載go語言安裝包golang

http://blog.csdn.net/liuhongwei123888/article/details/8512815編程

 

 

Let's go! (Ubuntu下搭建Go語言環境)

http://www.cnblogs.com/Liar/p/3551080.htmljson

 

go 編程環境搭建及vim設置vim

http://blog.chinaunix.net/uid-24774106-id-3964461.htmlwindows

 

golang的vim工做環境配置

http://my.oschina.net/goskyblue/blog/192647設計模式

 

windows環境搭建:數組

Go語言的開發環境配置的經驗總結

http://blog.csdn.net/wolinxuebin/article/details/7724049

Go語言_eclipse環境搭建

http://www.cnblogs.com/yjf512/archive/2012/06/19/2555248.html

//calc.go
package main
import "os"// 用於得到命令行參數os.Args
import "fmt"
import "simplemath"
import "strconv"
var Usage = func() {
fmt.Println("USAGE: calc command [arguments] ...")
fmt.Println("\nThe commands are:\n\tadd\tAddition of two values.\n\tsqrt\tSquare
root of a non-negative value.")
}
func main() {
args := os.Args
if args == nil || len(args) < 2 {
Usage()
return
}
switch args[0] {
case "add":
if len(args) != 3 {
fmt.Println("USAGE: calc add <integer1><integer2>")
return
}
v1, err1 := strconv.Atoi(args[1])
v2, err2 := strconv.Atoi(args[2])
if err1 != nil || err2 != nil {
fmt.Println("USAGE: calc add <integer1><integer2>")
return
}
ret := simplemath.Add(v1, v2)
fmt.Println("Result: ", ret)
case "sqrt":
if len(args) != 2 {
fmt.Println("USAGE: calc sqrt <integer>")
return
}
v, err := strconv.Atoi(args[1])
if err != nil {
fmt.Println("USAGE: calc sqrt <integer>")
return
}
ret := simplemath.Sqrt(v)
fmt.Println("Result: ", ret)
default:
Usage()
}

代碼清單1-6
add.go
// add.go
package simplemath
func Add(a int, b int) int {
return a + b
}

代碼清單1-7
add_test.go
// add_test.go
package simplemath
import "testing"
func TestAdd1(t *testing.T) {
r := Add(1, 2)
if r != 3 {
t.Errorf("Add(1, 2) failed. Got %d, expected 3.", r)
}
}
代碼清單1-8 sqrt.go
// sqrt.go
package simplemath
import "math"
func Sqrt(i int) int {
v := math.Sqrt(float64(i))
return int(v)
}
代碼清單1-9 sqrt_test.go
// sqrt_test.go
package simplemath
import "testing"
func TestSqrt1(t *testing.T) {
v := Sqrt(16)
if v != 4 {
t.Errorf("Sqrt(16) failed. Got %v, expected 4.", v)
}
}

爲了可以構建這個工程,
須要先把這個工程的根目錄加入到環境變量GOPATH中。
假設calcproj
目錄位於~/goyard下,則應編輯~/.bashrc文件,並添加下面這行代碼:
export GOPATH=~/goyard/calcproj
而後執行如下命令應用該設置:
$ source ~/.bashrc
GOPATH和PATH環境變量同樣,也能夠接受多個路徑,而且路徑和路徑之間用冒號分割。
設置完 GOPATH 後 ,如今咱們開始構建工程。假設咱們但願把生成的可執行文件放 到
calcproj/bin目錄中,須要執行的一系列指令以下:


cd ~/goyard/calcproj
mkdir bin
cd bin
go build calc
順利的話,將在該目錄下發現生成的一個叫作calc的可執行文件,執行該文件以查看幫助信
息並進行算術運算:

$ ./calc
USAGE: calc command [arguments] ...
The commands are:
addAddition of two values.
sqrtSquare root of a non-negative value.
$ ./calc add 2 3
Result: 5
$ ./calc sqrt 9
Result: 3
從上面的構建過程當中能夠看到,真正的構建命令就一句:

go build calc

 

 

go編譯靜態程序:

1 yingc@yingc:~/gcyin/study/go/test$ ll
2 total 580
3 drwxrwxr-x 2 yingc yingc   4096  7月 25 15:16 ./
4 drwxrwxr-x 3 yingc yingc   4096  7月 16 20:24 ../
5 -rw-rw-r-- 1 yingc yingc    101  7月 16 20:30 hello.go
6 -rwxrwxr-x 1 yingc yingc 577952  7月 25 15:16 main*
7 yingc@yingc:~/gcyin/study/go/test$ file main 
8 main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
9 yingc@yingc:~/gcyin/study/go/test$ go build -o main  -ldflags -static

 

 

go語言學習小知識點:

2. 閉包
Go的匿名函數是一個閉包,下面咱們先來了解一下閉包的概念、價值和應用場景。
 基本概念
閉包是能夠包含自由(未綁定到特定對象)變量的代碼塊,這些變量不在這個代碼塊內或者
任何全局上下文中定義,而是在定義代碼塊的環境中定義。要執行的代碼塊(因爲自由變量包含
在代碼塊中,因此這些自由變量以及它們引用的對象沒有被釋放)爲自由變量提供綁定的計算環
境(做用域)

 閉包的價值
閉包的價值在於能夠做爲函數對象或者匿名函數,對於類型系統而言,這意味着不只要表示
數據還要表示代碼。支持閉包的多數語言都將函數做爲第一級對象,就是說這些函數能夠存儲到
變量中做爲參數傳遞給其餘函數,最重要的是可以被函數動態建立和返回。
 Go語言中的閉包
Go語言中的閉包一樣也會引用到函數外的變量。閉包的實現確保只要閉包還被使用,那麼
被閉包引用的變量會一直存在,如代碼清單2-5所示。
代碼清單2-5
closure.go
package main
import (
"fmt"
)
func main() {
var j int = 5
a := func()(func()) {
var i int = 10
return func() {
fmt.Printf("i, j: %d, %d\n", i, j)
}
}()
a()
j *= 2
a()
}
上述例子的執行結果是:
1
i, j: 10, 5
i, j: 10, 10
在上面的例子中,變量a指向的閉包函數引用了局部變量i和j,i的值被隔離,在閉包外不
能被修改,改變j的值之後,再次調用a,發現結果是修改過的值。
在變量a指向的閉包函數中,只有內部的匿名函數才能訪問變量i,而沒法經過其餘途徑訪問到,所以保證了i的安全性。





2.5.3 不定參數
1. 不定參數類型
不定參數是指函數傳入的參數個數爲不定數量。爲了作到這點,首先須要將函數定義爲接受
不定參數類型:
func myfunc(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}

這段代碼的意思是,函數myfunc()接受不定數量的參數,這些參數的類型所有是int,所
以它能夠用以下方式調用:

myfunc(2, 3, 4)
myfunc(1, 3, 7, 13)
形如...type格式的類型只能做爲函數的參數類型存在,而且必須是最後一個參數。它是一
個語法糖(syntactic sugar)
,即這種語法對語言的功能並無影響,可是更方便程序員使用。通
常來講,使用語法糖可以增長程序的可讀性,從而減小程序出錯的機會。
從內部實現機理上來講,類型...type本質上是一個數組切片,也就是[]type,這也是爲
什麼上面的參數args能夠用for循環來得到每一個傳入的參數。
假如沒有...type這樣的語法糖,開發者將不得不這麼寫:
func myfunc2(args []int) {
for _, arg := range args {
fmt.Println(arg)
}
}
從函數的實現角度來看,這沒有任何影響,該怎麼寫就怎麼寫。但從調用方來講,情形則完
全不一樣:

myfunc2([]int{1, 3, 7, 13})
你會發現,咱們不得不加上[]int{}來構造一個數組切片實例。可是有了...type這個語法糖,
咱們就不用本身來處理了。
2. 不定參數的傳遞
假設有另外一個變參函數叫作myfunc3(args ...int),
下面的例子演示瞭如何向其傳遞變參:
func myfunc(args ...int) {
// 按原樣傳遞
myfunc3(args...)
// 傳遞片斷,實際上任意的int slice均可以傳進去
myfunc3(args[1:]...)
}
3. 任意類型的不定參數
以前的例子中將不定參數類型約束爲 int ,若是你但願傳任意類型,能夠指定類型爲
interface{}。下面是Go語言標準庫中fmt.Printf()的函數原型:
func Printf(format string, args ...interface{}) {
// ...
}
interface{}傳遞任意類型數據是Go語言的慣例用法。使用interface{}仍然是類型安
全的,這和 C/C++ 不太同樣。

package main
import "fmt"
func MyPrintf(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "is
case string:
fmt.Println(arg, "is
case int64:
fmt.Println(arg, "is
default:
fmt.Println(arg, "is
}
}
}
an int value.")
a string value.")
an int64 value.")
an unknown type.")
func main() {
var v1 int = 1
var v2 int64 = 234
var v3 string = "hello"
var v4 float32 = 1.234

MyPrintf(v1, v2, v3,v4)
}

該程序的輸出結果爲:
1 is an int value.
234 is an int64 value.
hello is a string value.
1.234 is an unknown type.






面向對象編程:
在C++ 語言中其實也有相似的功能,那就是虛基類,可是它很是讓人難以理解,通常C++的
開發者都會遺忘這個特性。相比之下,Go語言以一種很是容易理解的方式提供了一些本來指望
用虛基類才能解決的設計難題。
在Go語言官方網站提供的Effective Go中曾提到匿名組合的一個小价值,值得在這裏再提一
下。首先咱們能夠定義以下的類型,它匿名組合了一個log.Logger指針:
type Job struct {
Command string
*log.Logger
}
在合適的賦值後,咱們在Job類型的全部成員方法中能夠很溫馨地借用全部log.Logger提
供的方法。好比以下的寫法:

func (job *Job)Start() {
job.Log("starting now...")
... // 作一些事情
job.Log("started.")
}
對於Job的實現者來講,他甚至根本就不用意識到log.Logger類型的存在,這就是匿名組合的
魅力所在。在實際工做中,只有合理利用才能最大發揮這個功能的價值。
須要注意的是,無論是非匿名的類型組合仍是匿名組合,被組合的類型所包含的方法雖然都
升級成了外部這個組合類型的方法,但其實它們被組合方法調用時接收者並無改變。好比上面
這個 Job 例子,即便組合後調用的方式變成了 job.Log(...) ,但 Log 函數的接收者仍然是
log.Logger指針,所以在Log中不可能訪問到job的其餘成員方法和變量。


3.4 可見性 1 Go語言對關鍵字的增長很是吝嗇,其中沒有private、protected、public這樣的關鍵 字。要使某個符號對其餘包(package)可見(便可以訪問) ,須要將該符號定義爲以大寫字母 開頭,如: 2 type Rect struct { X, Y float64 Width, Height float64 } 這樣,Rect類型的成員變量就所有被導出了,能夠被全部其餘引用了Rect所在包的代碼訪問到。 成員方法的可訪問性遵循一樣的規則,例如: func (r *Rect) area() float64 { return r.Width * r.Height } 這樣,Rect的area()方法只能在該類型所在的包內使用。 須要注意的一點是,Go語言中符號的可訪問性是包一級的而不是類型一級的。在上面的例 子中,儘管area()是Rect的內部方法,但同一個包中的其餘類型也均可以訪問到它。這樣的可 訪問性控制很粗曠,很特別,可是很是實用。若是Go語言符號的可訪問性是類型一級的,少不 了還要加上friend這樣的關鍵字,以表示兩個類是朋友關係,能夠訪問彼此的私有成員。

3.5
接口

即便另外有一個接口IFoo2實現了與IFoo徹底同樣的接口方法甚至名字也叫IFoo只不過位
於不一樣的名字空間下,編譯器也會認爲上面的類Foo只實現了IFoo而沒有實現IFoo2接口。
這類接口咱們稱爲侵入式接口。「侵入式」的主要表如今於實現類須要明確聲明本身實現了
某個接口。

3.5.3 接口賦值
接口賦值在Go語言中分爲以下兩種狀況:
 將對象實例賦值給接口;
 將一個接口賦值給另外一個接口。


先討論將某種類型的對象實例賦值給接口,這要求該對象實例實現了接口要求的全部方法

咱們再來討論另外一種情形:將一個接口賦值給另外一個接口。在Go語言中,只要兩個接口擁
有相同的方法列表(次序不一樣沒關係),那麼它們就是等同的,能夠相互賦值。

接口賦值並不要求兩個接口必須等價。若是接口A的方法列表是接口B的方法列表的子集,
那麼接口B能夠賦值給接口A。

3.5.4 接口查詢
有辦法讓上面的Writer接口轉換爲two.IStream接口麼?有。那就是咱們即將討論的接口
查詢語法,代碼以下:
var file1 Writer = ...
if file5, ok := file1.(two.IStream); ok {
...
}
這個if語句檢查file1接口指向的對象實例是否實現了two.IStream接口,若是實現了,則執
行特定的代碼。
接口查詢是否成功,要在運行期纔可以肯定。它不像接口賦值,編譯器只須要經過靜態類型
檢查便可判斷賦值是否可行。


3.5.7 Any類型
因爲Go語言中任何對象實例都知足空接口interface{},因此interface{}看起來像是可
以指向任何對象的Any類型,以下:


var
var
var
var
var
v1
v2
v3
v4
v5
interface{}
interface{}
interface{}
interface{}
interface{}
=
=
=
=
=

// 將int類型賦值給interface{}
"abc"
// 將string類型賦值給interface{}
&v2
// 將*interface{}類型賦值給interface{}
struct{ X int }{1}
&struct{ X int }{1}


// 將int類型賦值給interface{}
"abc"
// 將string類型賦值給interface{}
&v2
// 將*interface{}類型賦值給interface{}
struct{ X int }{1}
&struct{ X int }{1}
5
6
當函數能夠接受任意的對象實例時,咱們會將其聲明爲interface{},最典型的例子是標
準庫fmt中PrintXXX系列的函數,例如:
func Printf(fmt string, args ...interface{})
func Println(args ...interface{})
...
整體來講,interface{}相似於COM中的IUnknown,咱們剛開始對其一無所知,但能夠通
過接口查詢和類型查詢逐步瞭解它。


併發編程

協程。協程(Coroutine)本質上是一種用戶態線程,不須要操做系統來進行搶佔式調度,
且在真正的實現中寄存於線程中,所以,系統開銷極小,能夠有效提升線程的任務併發
性,而避免多線程的缺點。使用協程的優勢是編程簡單,結構清晰;缺點是須要語言的
支持,若是不支持,則須要用戶在程序中自行實現調度器。目前,原生支持協程的語言
還不多。

消息傳遞系統

對線程間共享狀態的各類操做都被封裝在線程之間傳遞的消息中,這一般要求:發送消息時
對狀態進行復制,而且在消息傳遞的邊界上交出這個狀態的全部權。從邏輯上來看,這個操做
與共享內存系統中執行的原子更新操做相同,但從物理上來看則很是不一樣。因爲須要執行復制
操做,因此大多數消息傳遞的實如今性能上並不優越,但線程中的狀態管理工做一般會變得更
爲簡單。

4.2
協程
執行體是個抽象的概念,在操做系統層面有多個概念與之對應,好比操做系統本身掌管的
進程(process)、進程內的線程(thread)以及進程內的協程(coroutine,也叫輕量級線程)
。與傳統的系統級線程和進程相比,協程的最大優點在於其「輕量級」
,能夠輕鬆建立上百萬個而不會致使系統資源衰竭,而線程和進程一般最多也不能超過1萬個。這也是協程也叫輕量級線程的緣由。
多數語言在語法層面並不直接支持協程,而是經過庫的方式支持,但用庫的方式支持的功能
也並不完整,好比僅僅提供輕量級線程的建立、銷燬與切換等能力。若是在這樣的輕量級線程中
調用一個同步 IO 操做,好比網絡通訊、本地文件讀寫,都會阻塞其餘的併發執行輕量級線程,
從而沒法真正達到輕量級線程自己指望達到的目標。
Go 語言在語言級別支持輕量級線程,叫goroutine。Go 語言標準庫提供的全部系統調用操做(固然也包括全部同步 IO 操做)
,都會出讓 CPU 給其餘goroutine。這讓事情變得很是簡單,讓輕
量級線程的切換管理不依賴於系統的線程和進程,也不依賴於CPU的核心數量



4.5
channel

channel是Go語言在語言級別提供的goroutine間的通訊方式。咱們可使用channel在兩個或
多個goroutine之間傳遞消息。channel是進程內的通訊方式,所以經過channel傳遞對象的過程和調
用函數時的參數傳遞行爲比較一致,好比也能夠傳遞指針等。若是須要跨進程通訊,咱們建議用
分佈式系統的方法來解決,好比使用Socket或者HTTP等通訊協議。Go語言對於網絡方面也有非
常完善的支持。

4.5.3 緩衝機制
以前咱們示範建立的都是不帶緩衝的channel,這種作法對於傳遞單個數據的場景能夠接受,
但對於須要持續傳輸大量數據的場景就有些不合適了。接下來咱們介紹如何給channel帶上緩衝,
從而達到消息隊列的效果。
要建立一個帶緩衝的channel,其實也很是容易:
c := make(chan int, 1024)
在調用make()時將緩衝區大小做爲第二個參數傳入便可,好比上面這個例子就建立了一個大小
爲1024的int類型channel,即便沒有讀取方,寫入方也能夠一直往channel裏寫入,在緩衝區被
填完以前都不會阻塞。

從帶緩衝的channel中讀取數據可使用與常規非緩衝channel徹底一致的方法,但咱們也可
以使用range關鍵來實現更爲簡便的循環讀取:
for i := range c {
fmt.Println("Received:", i)
}


4.5.4 超時機制
Go語言沒有提供直接的超時處理機制,但咱們能夠利用select機制。雖然select機制不是
專爲超時而設計的,卻能很方便地解決超時問題。由於select的特色是隻要其中一個case已經
完成,程序就會繼續往下執行,而不會考慮其餘case的狀況。
基於此特性,咱們來爲channel實現超時機制:
// 首先,咱們實現並執行一個匿名的超時等待函數
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // 等待1秒鐘
timeout <- true
}()
// 而後咱們把timeout這個channel利用起來
select {
case <-ch:
// 從ch中讀取到數據
case <-timeout:
// 一直沒有從ch中讀取到數據,但從timeout中讀取到了數據
}

這樣使用select機制能夠避免永久等待的問題,由於程序會在timeout中獲取到一個數據
後繼續執行,不管對ch的讀取是否還處於等待狀態,從而達成1秒超時的效果。
這種寫法看起來是一個小技巧,但倒是在Go語言開發中避免channel通訊超時的最有效方法。
在實際的開發過程當中,這種寫法也須要被合理利用起來,從而有效地提升代碼質量。


4.5.5 channel的傳遞
須要注意的是,在Go語言中channel自己也是一個原生類型,與map之類的類型地位同樣,因
此channel自己在定義後也能夠經過channel來傳遞。
咱們可使用這個特性來實現*nix上很是常見的管道(pipe)特性。管道也是使用很是普遍
的一種設計模式,好比在處理數據時,咱們能夠採用管道設計,這樣能夠比較容易以插件的方式
增長數據的處理流程。
下面咱們利用channel可被傳遞的特性來實現咱們的管道。
爲了簡化表達,
咱們假設在管道中
傳遞的數據只是一個整型數,在實際的應用場景中這一般會是一個數據塊。
首先限定基本的數據結構:
type PipeData struct {
value int
handler func(int) int
next chan int
}
而後咱們寫一個常規的處理函數。咱們只要定義一系列PipeData的數據結構並一塊兒傳遞給
這個函數,就能夠達到流式處理數據的目的:
func handle(queue chan *PipeData) {
for data := range queue {
data.next <- data.handler(data.value)
}
}
這裏咱們只給出了大概的樣子,限於篇幅再也不展開。同理,利用channel的這個可傳遞特性,
咱們能夠實現很是強大、靈活的系統架構。相比之下,在C++、Java、C#中,要達成這樣的效果,
一般就意味着要設計一系列接口。
與Go語言接口的非侵入式相似,channel的這些特性也能夠大大下降開發者的心智成本,用
一些比較簡單卻實用的方式來達成在其餘語言中須要使用衆多技巧才能達成的效果。

4.5.6 單向channel
顧名思義,單向channel只能用於發送或者接收數據。channel自己必然是同時支持讀寫的,
不然根本無法用。假如一個channel真的只能讀,那麼確定只會是空的,由於你沒機會往裏面寫數
據。同理,若是一個channel只容許寫,即便寫進去了,也沒有絲毫意義,由於沒有機會讀取裏面
的數據。所謂的單向channel概念,其實只是對channel的一種使用限制。

咱們在將一個channel變量傳遞到一個函數時,能夠經過將其指定爲單向channel變量,從
而 限 制 該 函 數 中 可 以 對 此 channel 的 操 做 , 比 如 只 能 往 這 個 channel 寫 , 或 者 只 能 從 這 個
channel讀。
單向channel變量的聲明很是簡單,以下:
var ch1 chan int
// ch1是一個正常的channel,不是單向的
var ch2 chan<- float64// ch2是單向channel,只用於寫float64數據
var ch3 <-chan int
// ch3是單向channel,只用於讀取int數據
那麼單向channel如何初始化呢?以前咱們已經提到過,channel是一個原生類型,所以不只
支持被傳遞,還支持類型轉換。只有在介紹了單向channel的概念後,讀者纔會明白類型轉換對於
channel的意義:就是在單向channel和雙向channel之間進行轉換。示例以下:
ch4 := make(chan int)
ch5 := <-chan int(ch4) // ch5就是一個單向的讀取channel
ch6 := chan<- int(ch4) // ch6 是一個單向的寫入channel
基於ch4,咱們經過類型轉換初始化了兩個單向channel:單向讀的ch5和單向寫的ch6。
爲何要作這樣的限制呢?從設計的角度考慮,全部的代碼應該都遵循「最小權限原則」
,
從而避免不必地使用氾濫問題,
進而致使程序失控。
寫過C++程序的讀者確定就會聯想起const
指針的用法。非const指針具有const指針的全部功能,將一個指針設定爲const就是明確告訴
函數實現者不要試圖對該指針進行修改。單向channel也是起到這樣的一種契約做用。
下面咱們來看一下單向channel的用法:
func Parse(ch <-chan int) {
for value := range ch {
fmt.Println("Parsing value", value)
}
}
除非這個函數的實現者無恥地使用了類型轉換,不然這個函數就不會由於各類緣由而對ch
進行寫,避免在ch中出現非指望的數據,從而很好地實踐最小權限原則。

4.5.7 關閉channel
關閉channel很是簡單,直接使用Go語言內置的close()函數便可:

close(ch)
在介紹瞭如何關閉channel以後,咱們就多了一個問題:如何判斷一個channel是否已經被關
閉?咱們能夠在讀取的時候使用多重返回值的方式:
x, ok := <-ch
這個用法與map中的按鍵獲取value的過程比較相似,只須要看第二個bool返回值便可,
果返回值是false則表示ch已經被關閉。

4.6
多核並行化

4.7
出讓時間片
咱們能夠在每一個goroutine中控制什麼時候主動出讓時間片給其餘goroutine,這可使用runtime
包中的Gosched()函數實現。
實際上,若是要比較精細地控制goroutine的行爲,就必須比較深刻地瞭解Go語言開發包中
runtime包所提供的具體功能。

4.8
同步
咱們以前喊過一句口號,倡導用通訊來共享數據,而不是經過共享數據來進行通訊,但考慮到即便成功地用channel來做爲通訊手段,
仍是避免不了多個goroutine之間共享數據的問題,Go 語言的設計者雖然對channel有極高的指望,但也提供了妥善的資源鎖方案。


4.8.1 同步鎖


Go語言包中的sync包提供了兩種鎖類型:sync.Mutex和sync.RWMutex。Mutex是最簡單
的一種鎖類型,同時也比較暴力,當一個goroutine得到了Mutex後,其餘goroutine就只能乖乖等
到這個goroutine釋放該Mutex。RWMutex相對友好些,是經典的單寫多讀模型。在讀鎖佔用的情
況下,會阻止寫,但不阻止讀,也就是多個goroutine可同時獲取讀鎖(調用RLock()方法;而寫

(調用Lock()方法)
會阻止任何其餘goroutine
(不管讀和寫)
進來,
整個鎖至關於由該goroutine
獨佔。從RWMutex的實現看,RWMutex類型其實組合了Mutex:
type RWMutex struct {
w Mutex
writerSem uint32
readerSem
uint32
readerCount int32
readerWait int32
}


4.8.2 全局惟一性操做
對於從全局的角度只須要運行一次的代碼,好比全局初始化操做,Go語言提供了一個Once
類型來保證全局的惟一性操做,具體代碼以下:
var a string
var once sync.Once
func setup() {
a = "hello, world"
}
func doprint() {
once.Do(setup)
print(a)
}
func twoprint() {
go doprint()
go doprint()
}
若是這段代碼沒有引入Once,
setup()將會被每個goroutine先調用一次,
這至少對於這個
例子是多餘的。在現實中,咱們也常常會遇到這樣的狀況。Go語言標準庫爲咱們引入了Once類
型以解決這個問題。 once 的 Do()方法能夠保證在全局範圍內只調用指定的函數一次(這裏指
setup() 函數)
,並且全部其餘goroutine在調用到此語句時,將會先被阻塞,直至全局惟一的
once.Do()調用結束後才繼續。
這個機制比較輕巧地解決了使用其餘語言時開發者不得不自行設計和實現這種Once效果的
難題,也是Go語言爲併發性編程作了儘可能多考慮的一種體現。


第五章:
網絡編程
Go語言標準庫裏提供的net包,支持基
於IP層、TCP/UDP層及更高層面(如HTTP、FTP、SMTP)的網絡操做,其中用於IP層的稱爲Raw
Socket。


Go語言標準庫對此過程進行了抽象和封裝。不管咱們指望使用什麼協議創建什麼形式的連
接,都只須要調用net.Dial()便可。
5.1.1 Dial()函數
Dial()函數的原型以下:
func Dial(net, addr string) (Conn, error)
其中net參數是網絡協議的名字,addr參數是IP地址或域名,而端口號以「:」的形式跟隨在地址
或域名的後面,端口號可選。若是鏈接成功,返回鏈接對象,不然返回error。
咱們來看一下幾種常見協議的調用方式。
TCP連接:
conn, err := net.Dial("tcp", "192.168.0.10:2100")
UDP連接:
conn, err := net.Dial("udp", "192.168.0.12:975")
ICMP連接(使用協議名稱):
conn, err := net.Dial("ip4:icmp", "www.baidu.com")
ICMP連接(使用協議編號)
:

conn, err := net.Dial("ip4:1", "10.0.0.3")

5.1.4 更豐富的網絡通訊
實際上,Dial()函數是對DialTCP()、DialUDP()、DialIP()和DialUnix()的封裝。我
們也能夠直接調用這些函數,它們的功能是一致的。這些函數的原型以下:

func
DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error)
func
DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err error)
func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error)
func DialUnix(net string, laddr, raddr *UnixAddr) (c *UnixConn, err error)


代碼清單5-3 simplehttp2.go
package main
import (
"net"
"os"
"fmt"
"io/ioutil"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
os.Exit(1)
}
service := os.Args[1]
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
checkError(err)
conn, err := net.DialTCP("tcp", nil, tcpAddr)
checkError(err)
_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
checkError(err)
result, err := ioutil.ReadAll(conn)
checkError(err)
fmt.Println(string(result))
os.Exit(0)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
與以前使用Dail()的例子相比,這裏有兩個不一樣:
 net.ResolveTCPAddr(),用於解析地址和端口號;
 net.DialTCP(),用於創建連接。
這兩個函數在Dial()中都獲得了封裝。
此外,net包中還包含了一系列的工具函數,合理地使用這些函數能夠更好地保障程序的
質量。
驗證IP地址有效性的代碼以下:
func net.ParseIP()
建立子網掩碼的代碼以下:
func IPv4Mask(a, b, c, d byte) IPMask
獲取默認子網掩碼的代碼以下:
func (ip IP) DefaultMask() IPMask
根據域名查找IP的代碼以下:
func ResolveIPAddr(net, addr string) (*IPAddr, error)
func LookupHost(name string) (cname string, addrs []string, err error);


5.2
HTTP 編程
HTTP(HyperText Transfer Protocol,超文本傳輸協議)是互聯網上應用最爲普遍的一種網絡
協議,定義了客戶端和服務端之間請求與響應的傳輸標準。
Go語言標準庫內建提供了 net/http 包,涵蓋了HTTP客戶端和服務端的具體實現。使用
net/http包,咱們能夠很方便地編寫HTTP客戶端或服務端的程序。
閱讀本節內容,讀者須要具有以下知識點:
 瞭解 HTTP 基礎知識
 瞭解 Go 語言中接口的用法
5.2.1 HTTP客戶端
Go內置的net/http包提供了最簡潔的HTTP客戶端實現,咱們無需藉助第三方網絡通訊庫
(好比libcurl)就能夠直接使用HTTP中用得最多的GET和POST方式請求數據。
1. 基本方法
net/http包的Client類型提供了以下幾個方法,讓咱們能夠用最簡潔的方式實現 HTTP
請求:
func (c *Client)
func (c *Client)
error)
func (c *Client)
func (c *Client)
func (c *Client)
Get(url string) (r *Response, err error)
Post(url string, bodyType string, body io.Reader) (r *Response, err
PostForm(url string, data url.Values) (r *Response, err error)
Head(url string) (r *Response, err error)
Do(req *Request) (resp *Response, err error)
下面概要介紹這幾個方法。
 http.Get()
要請求一個資源,只需調用http.Get()方法(等價於http.DefaultClient.Get())即
可,示例代碼以下:
resp, err := http.Get("http://example.com/")
if err != nil {
// 處理錯誤 ...
return
}
defer resp.Body.close()
io.Copy(os.Stdout, resp.Body)
上面這段代碼請求一個網站首頁,並將其網頁內容打印到標準輸出流中。
 http.Post()
要以POST的方式發送數據,也很簡單,只需調用http.Post()方法並依次傳遞下面的3個
參數便可:
 請求的目標 URL
 將要 POST 數據的資源類型(MIMEType)
 數據的比特流([]byte形式)
下面的示例代碼演示瞭如何上傳一張圖片:
resp, err := http.Post("http://example.com/upload", "image/jpeg", &imageDataBuf)
if err != nil {
// 處理錯誤
return
}
if resp.StatusCode != http.StatusOK {
// 處理錯誤
return
}
// ...
 http.PostForm()
http.PostForm() 方法實現了標準編碼格式爲 application/x-www-form-urlencoded
的表單提交。下面的示例代碼模擬HTML表單提交一篇新文章:
resp, err := http.PostForm("http://example.com/posts",
{"article title"}, "content": {"article body"}})
if err != nil {
// 處理錯誤
return
}
// ...
 http.Head()
HTTP 中的 Head 請求方式代表只請求目標 URL 的頭部信息,即 HTTP Header 而不返回 HTTP
Body。Go 內置的 net/http 包一樣也提供了 http.Head() 方法,該方法同 http.Get() 方法一
樣,只需傳入目標 URL 一個參數便可。下面的示例代碼請求一個網站首頁的 HTTP Header信息:
resp, err := http.Head("http://example.com/")
 (*http.Client).Do()
在多數狀況下,http.Get()和http.PostForm() 就能夠知足需求,可是若是咱們發起的
HTTP 請求須要更多的定製信息,咱們但願設定一些自定義的 Http Header 字段,好比:
 設定自定義的"User-Agent",而不是默認的 "Go http package"
 傳遞 Cookie
此時可使用net/http包http.Client對象的Do()方法來實現:
req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("User-Agent", "Gobook Custom User-Agent")
// ...
client := &http.Client{ //... }
resp, err := client.Do(req)
// ...
2. 高級封裝
除了以前介紹的基本HTTP操做,Go語言標準庫也暴露了比較底層的HTTP相關庫,讓開發
者能夠基於這些庫靈活定製HTTP服務器和使用HTTP服務。
 自定義http.Client

前面咱們使用的http.Get()、http.Post()、http.PostForm()和http.Head()方法其
實都是在http.DefaultClient的基礎上進行調用的,好比http.Get()等價於http.Default-
Client.Get(),依次類推。
http.DefaultClient 在字面上就向咱們傳達了一個信息,既然存在默認的 Client,那麼
HTTP Client 大概是能夠自定義的。實際上確實如此,在net/http包中,的確提供了Client類
型。讓咱們來看一看http.Client類型的結構:
type Client struct {
// Transport用於肯定HTTP請求的建立機制。
// 若是爲空,將會使用DefaultTransport
Transport RoundTripper
// CheckRedirect定義重定向策略。
// 若是CheckRedirect不爲空,客戶端將在跟蹤HTTP重定向前調用該函數。
// 兩個參數req和via分別爲即將發起的請求和已經發起的全部請求,最先的
// 已發起請求在最前面。
// 若是CheckRedirect返回錯誤,客戶端將直接返回錯誤,不會再發起該請求。
// 若是CheckRedirect爲空,Client將採用一種確認策略,將在10個連續
// 請求後終止
CheckRedirect func(req *Request, via []*Request) error
// 若是Jar爲空,Cookie將不會在請求中發送,並會
// 在響應中被忽略
Jar CookieJar
}
在Go語言標準庫中,http.Client類型包含了3個公開數據成員:
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar

其中 Transport 類型必須實現 http.RoundTripper 接口。 Transport 指定了執行一個
HTTP 請求的運行機制,假若不指定具體的Transport,默認會使用http.DefaultTransport,
這意味着http.Transport也是能夠自定義的。net/http包中的http.Transport類型實現了
http.RoundTripper接口。
CheckRedirect函數指定處理重定向的策略。當使用 HTTP Client 的Get()或者是Head()
方法發送 HTTP 請求時,若響應返回的狀態碼爲 30x (好比 301 / 302 / 303 / 307)
,HTTP Client 會在遵循跳轉規則以前先調用這個CheckRedirect函數。
Jar可用於在 HTTP Client 中設定 Cookie,Jar的類型必須實現了 http.CookieJar 接口,
該接口預約義了 SetCookies()和Cookies()兩個方法。若是 HTTP Client 中沒有設定 Jar,
Cookie將被忽略而不會發送到客戶端。實際上,咱們通常都用 http.SetCookie() 方法來設定
Cookie。
使用自定義的http.Client及其Do()方法,咱們能夠很是靈活地控制 HTTP 請求,好比發
送自定義 HTTP Header 或是改寫重定向策略等。建立自定義的 HTTP Client 很是簡單,具體代碼
以下:
client := &http.Client {
CheckRedirect: redirectPolicyFunc,
}
resp, err := client.Get("http://example.com")
// ...
req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("User-Agent", "Our Custom User-Agent")
req.Header.Add("If-None-Match", `W/"TheFileEtag"`)
resp, err := client.Do(req)
// ...

 自定義 http.Transport
在http.Client 類型的結構定義中,
咱們看到的第一個數據成員就是一個 http.Transport
對象,該對象指定執行一個 HTTP 請求時的運行規則。下面咱們來看看 http.Transport 類型
的具體結構:
type Transport struct {
// Proxy指定用於針對特定請求返回代理的函數。
// 若是該函數返回一個非空的錯誤,請求將終止並返回該錯誤。
// 若是Proxy爲空或者返回一個空的URL指針,將不使用代理
Proxy func(*Request) (*url.URL, error)
// Dial指定用於建立TCP鏈接的dail()函數。
// 若是Dial爲空,將默認使用net.Dial()函數
Dial func(net, addr string) (c net.Conn, err error)
// TLSClientConfig指定用於tls.Client的TLS配置。
// 若是爲空則使用默認配置
TLSClientConfig *tls.Config
DisableKeepAlives bool
DisableCompression bool
// 若是MaxIdleConnsPerHost爲非零值,它用於控制每一個host所須要
// 保持的最大空閒鏈接數。若是該值爲空,則使用DefaultMaxIdleConnsPerHost
MaxIdleConnsPerHost int
// ...
}


在上面的代碼中,咱們定義了 http.Transport 類型中的公開數據成員,下面詳細說明其
中的各行代碼。
Proxy func(*Request) (*url.URL, error)
Proxy 指定了一個代理方法,該方法接受一個 *Request 類型的請求實例做爲參數並返回
一個最終的 HTTP 代理。若是 Proxy 未指定或者返回的 *URL 爲零值,將不會有代理被啓用。
Dial func(net, addr string) (c net.Conn, err error)
Dial 指定具體的dial()方法來建立 TCP 鏈接。若是不指定,默認將使用 net.Dial() 方法。
TLSClientConfig *tls.Config
SSL鏈接專用,TLSClientConfig 指定 tls.Client 所用的 TLS 配置信息,若是不指定,
也會使用默認的配置。
DisableKeepAlives bool
是否取消長鏈接,默認值爲 false,即啓用長鏈接。
DisableCompression bool
是否取消壓縮(GZip),默認值爲 false,即啓用壓縮。
MaxIdleConnsPerHost int
指定與每一個請求的目標主機之間的最大非活躍鏈接(keep-alive)數量。若是不指定,默認使
用 DefaultMaxIdleConnsPerHost 的常量值。
除了 http.Transport 類型中定義的公開數據成員之外,它同時還提供了幾個公開的成員
方法。

 func(t *Transport) CloseIdleConnections() 。該方法用於關閉全部非活躍的鏈接。
 func(t *Transport) RegisterProtocol(scheme string, rt RoundTripper)。
該方法可用於註冊並啓用一個新的傳輸協議,好比 WebSocket 的傳輸協議標準(ws),或者 FTP、File 協議等。
 func(t *Transport) RoundTrip(req *Request) (resp *Response, err error)。
用於實現 http.RoundTripper 接口。
自定義http.Transport也很簡單,以下列代碼所示:
tr := &http.Transport{
TLSClientConfig:
&tls.Config{RootCAs: pool},
DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")
Client和Transport在執行多個 goroutine 的併發過程當中都是安全的,但出於性能考慮,應
當建立一次後反覆使用。
 靈活的 http.RoundTripper 接口
在前面的兩小節中,咱們知道 HTTP Client 是能夠自定義的,而 http.Client 定義的第一
個 公 開 成 員 就 是 一 個 http.Transport 類 型 的 實 例 , 且 該 成 員 所 對 應 的 類 型 必 須 實 現
http.RoundTripper 接口。下面咱們來看看 http.RoundTripper 接口的具體定義:
type RoundTripper interface {
// RoundTrip執行一個單一的HTTP事務,返回相應的響應信息。
// RoundTrip函數的實現不該試圖去理解響應的內容。若是RoundTrip獲得一個響應,
// 不管該響應的HTTP狀態碼如何,都應將返回的err設置爲nil。非空的err
// 只意味着沒有成功獲取到響應。
// 相似地,RoundTrip也不該試圖處理更高級別的協議,好比重定向、認證和
// Cookie等。
//
// RoundTrip不該修改請求內容, 除非了是爲了理解Body內容。每個請求
// 的URL和Header域都應被正確初始化
RoundTrip(*Request) (*Response, error)
}
從上述代碼中能夠看到,http.RoundTripper接口很簡單,只定義了一個名爲RoundTrip
的方法。任何實現了 RoundTrip() 方法的類型便可實現http.RoundTripper接口。前面咱們
看到的http.Transport類型正是實現了 RoundTrip() 方法繼而實現了該接口。
http.RoundTripper 接口定義的 RoundTrip() 方法用於執行一個獨立的 HTTP 事務,接
受傳入的 \*Request 請求值做爲參數並返回對應的 \*Response 響應值,以及一個 error 值。
在實現具體的 RoundTrip() 方法時,不該該試圖在該函數裏邊解析 HTTP 響應信息。若響應成
功,error 的值必須爲nil,而與返回的 HTTP 狀態碼無關。若不能成功獲得服務端的響應,
error必須爲非零值。相似地,也不該該試圖在 RoundTrip() 中處理協議層面的相關細節,好比重定
向、認證或是 cookie 等。
非必要狀況下,不該該在 RoundTrip() 中改寫傳入的請求體(\*Request)
,請求體的內容(好比 URL 和 Header 等)必須在傳入 RoundTrip() 以前就已組織好並完成初始化。
一般,咱們能夠在默認的 http.Transport 之上包一層 Transport 並實現 RoundTrip()方法

 設計優雅的 HTTP Client
綜上示例講解能夠看到,Go語言標準庫提供的 HTTP Client 是至關優雅的。一方面提供了極
其簡單的使用方式,另外一方面又具有極大的靈活性。
Go語言標準庫提供的HTTP Client 被設計成上下兩層結構
一層是上述提到的 http.Client類及其封裝的基礎方法,咱們不妨將其稱爲「業務層」
。之因此稱爲業務層,是由於調用方一般只須要關心請求的業務邏輯自己,而無需關心非業務相關的技術細節,這些細節包括:
 HTTP 底層傳輸細節
 HTTP 代理
 gzip 壓縮
 鏈接池及其管理
 認證(SSL或其餘認證方式)
之 所 以 HTTP Client 可 以 作 到 這 麼 好 的 封 裝 性 , 是 因 爲 HTTP Client 在 底 層 抽 象 了
http.RoundTripper 接口,而http.Transport 實現了該接口,從而可以處理更多的細節,我
們不妨將其稱爲「傳輸層」。HTTP Client 在業務層初始化 HTTP Method、目標URL、請求參數、
請求內容等重要信息後,通過「傳輸層」「傳輸層」在業務層處理的基礎上補充其餘細節,而後,
再發起 HTTP 請求,接收服務端返回的 HTTP 響應。

5.2.2 HTTP服務端
本節咱們將介紹HTTP服務端技術,包括如何處理HTTP請求和HTTPS請求
1. 處理HTTP請求
使用 net/http 包提供的 http.ListenAndServe() 方法,能夠在指定的地址進行監聽,
開啓一個HTTP,服務端該方法的原型以下:
func ListenAndServe(addr string, handler Handler) error
該方法用於在指定的 TCP 網絡地址 addr 進行監聽,而後調用服務端處理程序來處理傳入的連
接請求。該方法有兩個參數:第一個參數 addr 即監聽地址;第二個參數表示服務端處理程序,
一般爲空,這意味着服務端調用 http.DefaultServeMux 進行處理,而服務端編寫的業務邏
輯處理程序 http.Handle() 或 http.HandleFunc() 默認注入 http.DefaultServeMux 中,
具體代碼以下:
http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
若是想更多地控制服務端的行爲,能夠自定義 http.Server,代碼以下:
s := &http.Server{
Addr:
":8080",
Handler:
myHandler,
ReadTimeout:
10 * time.Second,
WriteTimeout:
10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())

2. 處理HTTPS請求
net/http 包還提供 http.ListenAndServeTLS() 方法,用於處理 HTTPS 鏈接請求:
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler)
error
ListenAndServeTLS() 和 ListenAndServe()的行爲一致,
區別在於只處理HTTPS請求。此外,服務器上必須存在包含證書和與之匹配的私鑰的相關文件,好比certFile對應SSL證書
文件存放路徑,keyFile對應證書私鑰文件路徑。若是證書是由證書頒發機構簽署的,certFile
參數指定的路徑必須是存放在服務器上的經由CA認證過的SSL證書。
開啓 SSL 監聽服務也很簡單,以下列代碼所示:
http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil))

或者是:
ss := &http.Server{
Addr:":10443",
Handler:myHandler,
ReadTimeout:10 * time.Second,
WriteTimeout:10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(ss.ListenAndServeTLS("cert.pem", "key.pem"))

5.3
RPC 編程
RPC(Remote Procedure Call,遠程過程調用)是一種經過網絡從遠程計算機程序上請求服
務,而不須要了解底層網絡細節的應用程序通訊協議。RPC協議構建於TCP或UDP,或者是 HTTP
之上,容許開發者直接調用另外一臺計算機上的程序,而開發者無需額外地爲這個調用過程編寫網
絡通訊相關代碼,使得開發包括網絡分佈式程序在內的應用程序更加容易。
RPC 採用客戶端—服務器(Client/Server)的工做模式。請求程序就是一個客戶端(Client)
,而服務提供程序就是一個服務器(Server)。當執行一個遠程過程調用時,客戶端程序首先發送一
個帶有參數的調用信息到服務端,而後等待服務端響應。在服務端,服務進程保持睡眠狀態直到
客戶端的調用信息到達爲止。當一個調用信息到達時,服務端得到進程參數,計算出結果,並向
客戶端發送應答信息,而後等待下一個調用。最後,客戶端接收來自服務端的應答信息,得到進
程結果,而後調用執行並繼續進行。
net/rpc包容許 RPC 客戶端程序經過網絡或是其餘 I/O 鏈接調用一個遠端對象的公開方法
(必須是大寫字母開頭、可外部調用的)在 RPC 服務端,可將一個對象註冊爲可訪問的服務,
以後該對象的公開方法就可以以遠程的方式提供訪問。一個 RPC 服務端能夠註冊多個不一樣類型
的對象,但不容許註冊同一類型的多個對象。
一個對象中只有知足以下這些條件的方法,才能被 RPC 服務端設置爲可供遠程訪問:
 必須是在對象外部可公開調用的方法(首字母大寫)
;
 必須有兩個參數,且參數的類型都必須是包外部能夠訪問的類型或者是Go內建支持的類
型;
第二個參數必須是一個指針;
 方法必須返回一個error類型的值。
以上4個條件,能夠簡單地用以下一行代碼表示:
func (t *T) MethodName(argType T1, replyType *T2) error
在上面這行代碼中,類型T、T1 和 T2 默認會使用 Go 內置的 encoding/gob 包進行編碼解碼。
關於encoding/gob 包的內容,稍後咱們將會對其進行介紹。
該方法(MethodName)的第一個參數表示由 RPC 客戶端傳入的參數,第二個參數表示要返
回給RPC客戶端的結果,該方法最後返回一個 error 類型的值。
RPC 服務端能夠經過調用 rpc.ServeConn 處理單個鏈接請求。多數狀況下,經過 TCP 或
是 HTTP 在某個網絡地址上進行監聽來建立該服務是個不錯的選擇。

在 RPC 客戶端,Go 的 net/rpc 包提供了便利的 rpc.Dial() 和 rpc.DialHTTP() 方法
來與指定的 RPC 服務端創建鏈接。在創建鏈接以後,Go 的 net/rpc 包容許咱們使用同步或者
異步的方式接收 RPC 服務端的處理結果。調用 RPC 客戶端的 Call() 方法則進行同步處理,這
時候客戶端程序按順序執行,只有接收完 RPC 服務端的處理結果以後才能夠繼續執行後面的程
序。當調用 RPC 客戶端的 Go() 方法時,則能夠進行異步處理,RPC 客戶端程序無需等待服務
端的結果便可執行後面的程序,而當接收到 RPC 服務端的處理結果時,再對其進行相應的處理。
不管是調用 RPC 客戶端的 Call() 或者是 Go() 方法,都必須指定要調用的服務及其方法名稱,
以及一個客戶端傳入參數的引用,還有一個用於接收處理結果參數的指針

若是沒有明確指定 RPC 傳輸過程當中使用何種編碼解碼器,默認將使用 Go 標準庫提供的
encoding/gob 包進行數據傳輸。

5.3.2 Gob簡介
Gob 是 Go 的一個序列化數據結構的編碼解碼工具,在 Go 標準庫中內置encoding/gob包
以供使用。一個數據結構使用 Gob 進行序列化以後,可以用於網絡傳輸。與 JSON 或 XML 這種
基於文本描述的數據交換語言不一樣,Gob 是二進制編碼的數據流,而且 Gob 流是能夠自解釋的,
它在保證高效率的同時,也具有完整的表達能力。
做爲針對 Go 的數據結構進行編碼和解碼的專用序列化方法,這意味着 Gob 沒法跨語言使
用。在 Go 的net/rpc包中,傳輸數據所須要用到的編碼解碼器,默認就是 Gob。因爲 Gob 僅局
限於使用 Go 語言開發的程序,這意味着咱們只能用 Go 的 RPC 實現進程間通訊。然而,大多數
時候,咱們用 Go 編寫的 RPC 服務端(或客戶端),可能更但願它是通用的,與語言無關的,無
論是Python 、 Java 或其餘編程語言實現的 RPC 客戶端,都可與之通訊。

5.3.3 設計優雅的RPC接口
Go 的net/rpc很靈活,它在數據傳輸先後實現了編碼解碼器的接口定義。這意味着,開發
者能夠自定義數據的傳輸方式以及 RPC 服務端和客戶端之間的交互行爲
RPC 提供的編碼解碼器接口以下:
type ClientCodec interface {
WriteRequest(*Request, interface{}) error
ReadResponseHeader(*Response) error
ReadResponseBody(interface{}) error
Close() error
}
type ServerCodec interface {
ReadRequestHeader(*Request) error
ReadRequestBody(interface{}) error
WriteResponse(*Response, interface{}) error
Close() error
}
接口ClientCodec定義了 RPC 客戶端如何在一個 RPC 會話中發送請求和讀取響應。
客戶端程
序經過 WriteRequest() 方法將一個請求寫入到 RPC 鏈接中,並經過 ReadResponseHeader()
和 ReadResponseBody() 讀取服務端的響應信息。當整個過程執行完畢後,再經過 Close() 方
法來關閉該鏈接。
接口ServerCodec定義了 RPC 服務端如何在一個 RPC 會話中接收請求併發送響應。服務端
程序經過 ReadRequestHeader() 和 ReadRequestBody() 方法從一個 RPC 鏈接中讀取請求
信息,而後再經過 WriteResponse() 方法向該鏈接中的 RPC 客戶端發送響應。當完成該過程
後,經過 Close() 方法來關閉鏈接。
經過實現上述接口,咱們能夠自定義數據傳輸先後的編碼解碼方式,而不只僅侷限於 Gob。
一樣,能夠自定義RPC 服務端和客戶端的交互行爲。實際上,Go 標準庫提供的net/rpc/json
包,就是一套實現了rpc.ClientCodec和rpc.ServerCodec接口的 JSON-RPC 模塊。


5.4
JSON 處理
Go語言內建對JSON的支持。使用Go語言內置的encoding/json 標準庫,開發者能夠輕鬆
使用Go程序生成和解析JSON格式的數據。在Go語言實現JSON的編碼和解碼時,遵循RFC4627
協議標準。

5.4.1 編碼爲JSON格式
使用json.Marshal()函數能夠對一組數據進行JSON格式的編碼。json.Marshal()函數
的聲明以下:
func Marshal(v interface{}) ([]byte, error)
假若有以下一個Book類型的結構體:
type Book struct {
Title string
Authors []string
Publisher string
IsPublished bool
Price float
}
而且有以下一個 Book 類型的實例對象:
gobook := Book{
"Go語言編程",
["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
"XuDaoli"],
"ituring.com.cn",
true,
9.99
}
而後,咱們可使用 json.Marshal() 函數將gobook實例生成一段JSON格式的文本:
b, err := json.Marshal(gobook)
若是編碼成功,err 將賦於零值 nil,變量b 將會是一個進行JSON格式化以後的[]byte
類型:
b == []byte(`{
"Title": "Go語言編程",
"Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
"XuDaoli"],
"Publisher": "ituring.com.cn",
"IsPublished": true,
"Price": 9.99
}`)

當咱們調用json.Marshal(gobook)語句時,會遞歸遍歷gobook對象,若是發現gobook這個
數據結構實現了json.Marshaler接口且包含有效的值,Marshal()就會調用其MarshalJSON()
方法將該數據結構生成 JSON 格式的文本。Go語言的大多數數據類型均可以轉化爲有效的JSON文本,
channel、complex和函數這幾種類型除外
若是轉化前的數據結構中出現指針,那麼將會轉化指針所指向的值,若是指針指向的是零值,
那麼null將做爲轉化後的結果輸出

在Go中,JSON轉化先後的數據類型映射以下。
 布爾值轉化爲JSON後仍是布爾類型。
 浮點數和整型會被轉化爲JSON裏邊的常規數字。
 字符串將以UTF-8編碼轉化輸出爲Unicode字符集的字符串,
特殊字符好比<將會被轉義爲
\u003c。
 數組和切片會轉化爲JSON裏邊的數組,但[]byte類型的值將會被轉化爲 Base64 編碼後
的字符串,slice類型的零值會被轉化爲 null。
 結構體會轉化爲JSON對象,而且只有結構體裏邊以大寫字母開頭的可被導出的字段纔會
被轉化輸出,而這些可導出的字段會做爲JSON對象的字符串索引。
 轉 化一個 map 類 型 的數據 結構時 ,該 數據的 類型 必須是 map[string]T ( T 可 以是
encoding/json 包支持的任意數據類型)。

5.4.2 解碼JSON數據
可使用 json.Unmarshal() 函數將JSON格式的文本解碼爲Go裏邊預期的數據結構。
json.Unmarshal()函數的原型以下:
func Unmarshal(data []byte, v interface{}) error
該函數的第一個參數是輸入,即JSON格式的文本(比特序列),第二個參數表示目標輸出容器,用於存放解碼後的值。
要解碼一段JSON數據,首先須要在Go中建立一個目標類型的實例對象,用於存放解碼後的值:
var book Book
而後
調用 json.Unmarshal() 函數, []byte 類型的JSON數據做爲第一個參數傳入, book
實例變量的指針做爲第二個參數傳入:
若是 b 是一個有效的JSON數據並能和 book 結構對應起來,那麼JSON解碼後的值將會一一
存放到book結構體中。解碼成功後的 book 數據以下:
book := Book{
"Go語言編程",
["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
"XuDaoli"],
"ituring.com.cn",
true,
9.99
}

json.Unmarshal()函數會根據一個約定的順序查找目標結構中的字段,
若是找到一個即發生匹配。
假設一個JSON對象有個名爲"Foo"的索引,
要將"Foo"所對應的值填充到目標
結構體的目標字段上,json.Unmarshal()將會遵循以下順序進行查找匹配:
(1) 一個包含Foo標籤的字段;
(2) 一個名爲Foo的字段;
(3) 一個名爲 Foo 或者 Foo 或者除了首字母其餘字母不區分大小寫的名爲 Foo 的字段。
這些字段在類型聲明中必須都是以大寫字母開頭、可被導出的字段。
可是當JSON數據裏邊的結構和Go裏邊的目標類型的結構對不上時,會發生什麼呢?示例代
碼以下:
b := []byte(`{"Title": "Go語言編程", "Sales": 1000000}`)
var gobook Book
err := json.Unmarshal(b, &gobook)
若是JSON中的字段在Go目標類型中不存在,json.Unmarshal()函數在解碼過程當中會丟棄
該字段。在上面的示例代碼中,因爲Sales字段並無在Book類型中定義,因此會被忽略,只有
Title這個字段的值纔會被填充到gobook.Title中。
這個特性讓咱們能夠從同一段JSON數據中篩選指定的值填充到多個Go語言類型中。固然,
前提是已知JSON數據的字段結構。這也一樣意味着,目標類型中不可被導出的私有字段(非首
字母大寫)將不會受到解碼轉化的影響
但若是JSON的數據結構是未知的,應該如何處理呢?

5.4.3 解碼未知結構的JSON數據
咱們已經知道,Go語言支持接口。在Go語言裏,接口是一組預約義方法的組合,任何一個
類型都可經過實現接口預約義的方法來實現,且無需顯示聲明,因此沒有任何方法的空接口能夠
表明任何類型。換句話說,每個類型其實都至少實現了一個空接口。
Go內建這樣靈活的類型系統,向咱們傳達了一個頗有價值的信息:空接口是通用類型。如
果要解碼一段未知結構的JSON,
只需將這段JSON數據解碼輸出到一個空接口便可。
在解碼JSON數據的過程當中,JSON數據裏邊的元素類型將作以下轉換:
 JSON中的布爾值將會轉換爲Go中的bool類型;
 數值會被轉換爲Go中的float64類型;
 字符串轉換後仍是string類型;
 JSON數組會轉換爲[]interface{}類型;
 JSON對象會轉換爲map[string]interface{}類型;
 null值會轉換爲nil。
在Go的標準庫encoding/json包中,容許使用map[string]interface{}和[]interface{}
類型的值來分別存放未知結構的JSON對象或數組,示例代碼以下:

b := []byte(`{
"Title": "Go語言編程",
"Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
"XuDaoli"],
"Publisher": "ituring.com.cn",
"IsPublished": true,
"Price": 9.99,
"Sales": 1000000
}`)
var r interface{}
err := json.Unmarshal(b, &r)
在上述代碼中,r被定義爲一個空接口。json.Unmarshal() 函數將一個JSON對象解碼到
空接口r中,最終r將會是一個鍵值對的 map[string]interface{} 結構:
map[string]interface{}{
"Title": "Go語言編程",
"Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
"XuDaoli"],
"Publisher": "ituring.com.cn",
"IsPublished": true,
"Price": 9.99,
"Sales": 1000000
}
要訪問解碼後的數據結構,須要先判斷目標結構是否爲預期的數據類型:
gobook, ok := r.(map[string]interface{})
而後,咱們能夠經過for循環搭配range語句一一訪問解碼後的目標數據:

if ok {
for k, v := range gobook {
switch v2 := v.(type) {
case string:
..........
雖然有些煩瑣,但的確是一種解碼未知結構的JSON數據的安全方式。

5.4.4 JSON的流式讀寫
Go內建的encoding/json 包還提供Decoder和Encoder兩個類型,用於支持JSON數據的
流式讀寫,並提供NewDecoder()和NewEncoder()兩個函數來便於具體實現:
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder

代碼清單5-6從標準輸入流中讀取JSON數據,而後將其解碼,但只保留Title字段(書名)
,
再寫入到標準輸出流中。
代碼清單5-6 jsondemo.go
package main
import (
"encoding/json"
"log"
"os"
)
func main() {
dec := json.NewDecoder(os.Stdin)
enc := json.NewEncoder(os.Stdout)
for {
var v map[string]interface{}
if err := dec.Decode(&v); err != nil {
log.Println(err)
return
}
for k := range v {
if k != "Title" {
v[k] = nil, false
}
}
if err := enc.Encode(&v); err != nil {
log.Println(err)
}
}
}
使用Decoder 和Encoder對數據流進行處理能夠應用得更爲普遍些,好比讀寫 HTTP 鏈接、
WebSocket或文件等,Go的標準庫net/rpc/jsonrpc就是一個應用了Decoder和Encoder的實際例子。

5.5
網站開發
在這一節中,咱們將向你按部就班地講解怎樣使用Go進行Web開發。本節將圍繞一個簡單的
相冊程序進行,儘管示例程序比較簡單,但體現的都是使用Go開發網站的幾處關鍵環節,旨在
讓你係統瞭解基於原生的Go語言開發Web應用程序的基本思路及其相關細節的具體實現。


進階話題
  9.1
反射
反射(reflection)是在Java出現後迅速流行起來的一種概念。經過反射,你能夠獲取豐富的
類型信息,並能夠利用這些類型信息作很是靈活的工做。
在Java中,你能夠讀取配置並根據類型名稱建立對應的類型,這是一種常見的編程手法。
Java中的不少重要框架和技術(好比Spring/IoC、Hibernate、Struts)等都嚴重依賴於反射功能。雖然
時,使用Java EE時不少人都以爲很麻煩,好比須要配置大量XML格式的配置程序,但這畢竟不
是反射的錯,反而更加說明了反射所帶來的高可配置性
大多數現代的高級語言都以各類形式支持反射功能,除了一切以性能爲上的C++語言。Go
語言的反射實現了反射的大部分功能,但沒有像Java語言那樣內置類型工廠,
故而沒法作到像Java那樣經過類型字符串建立對象實例。
反射是把雙刃劍,功能強大但代碼可讀性並不理想。若非必要,咱們並不推薦使用反射,這
也是咱們把反射放到進階話題來介紹的緣由。

9.1.1 基本概念
Go語言中的反射與其餘語言有比較大的不一樣。首先咱們要理解兩個基本概念—— Type 和
Value,它們也是Go語言包中reflect空間裏最重要的兩個類型。咱們先看一下下面的定義:
type MyReader struct {
Name string
}
func (r MyReader)Read(p []byte) (n int, err error) {
// 實現本身的Read方法
}
由於MyReader類型實現了io.Reader接口的全部方法(其實就是一個Read()函數)
,因此MyReader實現了接口io.Reader。咱們能夠按以下方式來進行MyReader的實例化和賦值:
var reader io.Reader
reader = &MyReader{"a.txt"}
如今咱們能夠再來解釋一下什麼是Type,什麼是Value。對全部接口進行反射,均可以獲得一個包含Type和Value的信息結構。好比咱們對上例的
reader進行反射,也將獲得一個Type和Value,Type爲io.Reader,Value爲MyReader{"a.txt"}。
顧名思義,Type主要表達的是被反射的這個變量自己的類型信息, Value則爲該變量實例自己而的信息。

9.1.2 基本用法
經過使用Type和Value,咱們能夠對一個類型進行各項靈活的操做。接下來咱們分別演示反射的幾種最基本用途。
1. 獲取類型信息
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}
以上代碼將輸出以下的結果:
type: float64
Type和Value都包含了大量的方法,其中第一個有用的方法應該是Kind,這個方法返回該
類型的具體信息:Uint、Float64等。Value類型還包含了一系列類型方法,好比Int(),用於
返回對應的值。查看如下示例:
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())

fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
結果爲:
type: float64
kind is float64: true
value: 3.4

2. 獲取值類型
類型Type中有一個成員函數CanSet(),
其返回值爲bool類型。
若是你在注意到這個函數之
前就直接設置了值,頗有可能會收到一些看起來像異常的錯誤處理消息。
可能不少人會置疑爲何要有這麼個奇怪的函數,
能夠設置全部的域不是很好嗎?這裏先解
釋一下這個函數存在的緣由。
咱們在第2章中提到過Go語言中全部的類型都是值類型,即這些變量在傳遞給函數的時候將
發生一次複製。基於這個原則,咱們再次看一下下面的語句:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.Set(4.1)
最後一條語句試圖修改v的內容。是否能夠成功地將x的值改成4.1呢?先要理清v和x的關係。在
調用ValueOf()的地方,須要注意到x將會產生一個副本,所以ValueOf()內部對x的操做其實
都是對着x的一個副本。假如v容許調用Set(),那麼咱們也能夠想象出,被修改的將是這個x的
副本,而不是x自己。若是容許這樣的行爲,那麼執行結果將會很是困惑。調用明明成功了,爲
什麼x的值仍是原來的呢?爲了解決這個問題Go語言,引入了可設屬性這個概念(Settability)

若是 CanSet() 返回 false ,表示你不該該調用 Set() 和 SetXxx() 方法,不然會收到這樣的
錯誤:
panic: reflect.Value.SetFloat using unaddressable value
如今咱們知道,有些場景下不能使用反射修改值,那麼到底什麼狀況下能夠修改的呢?其實
這仍是跟傳值的道理相似。咱們知道,直接傳遞一個float到函數時,函數不能對外部的這個
float變量有任何影響,要想有影響的話,能夠傳入該float變量的指針。下面的示例小幅修改
了以前的例子,成功地用反射的方式修改了變量x的值:
var x float64 = 3.4
p := reflect.ValueOf(&x) // 注意:獲得X的地址
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:" , p.CanSet())
v := p.Elem()
fmt.Println("settability of v:" , v.CanSet())
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)



184 func test_reflect(){
185 ▸   var x float64 =3.4
186 ▸   fmt.Println("type:",reflect.TypeOf(x))
187 ▸   v :=reflect.ValueOf(x)
188 ▸   fmt.Println("v type:",v.Type())
189 ▸   fmt.Println("kind of float64:",v.Kind()==reflect.Float64)
190 ▸   fmt.Println("value float:",v.Float())
191 ▸   p :=reflect.ValueOf(&x)
192 ▸   fmt.Println("p type:",p.Type())
193 ▸   fmt.Println("setability of p:",p.CanSet())
194 ▸   v1 :=p.Elem()
195 ▸   fmt.Println("setability of v1:",v1.CanSet())
196 ▸   v1.SetFloat(4.1)
197 ▸   fmt.Println(v1.Interface())
198 ▸   fmt.Println(x)
199 }

go run hello.go
type: float64
v type: float64
kind of float64: true
value float: 3.4
p type: *float64
setability of p: false
setability of v1: true
4.1
4.1



9.1.3 對結構的反射操做

以前討論的都是簡單類型的反射操做,如今咱們討論一下結構的反射操做。下面的示例演示
瞭如何獲取一個結構中全部成員的值:
type T struct {
A int
B string
}
t := T{203, "mh203"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}

以上例子的輸出爲:
0: A int = 203
1: B string = mh203
能夠看出,對於結構的反射操做並無根本上的不一樣,只是用了Field()方法來按索引獲取
對應的成員。固然,在試圖修改爲員的值時,也須要注意可賦值屬性


9.2 語言交互性

  1 package main
  2
  3 import (
  4 ▸   "fmt"
  5 )
  6 //#include <stdio.h>
  7 //#include <stdlib.h>
  8 import "C"
  9
 10 func Random()  int {
 11 ▸   return int(C.random())
 12 }
 13 func Seed(i int) {
 14 ▸   C.srandom(C.uint(i))
 15 }
 16 func main() {
 17 ▸   Seed(100)
 18 ▸   fmt.Println("Random:",Random())
 19 }



事實上,根本就不存在一個名爲C的包。這個import語句其實就是一個信號,告訴Cgo它應
該開始工做了。作什麼事情呢?就是對應這條import語句以前的塊註釋中的C源代碼自動生成
包裝性質的Go代碼。

這時候咱們該注意到import語句前緊跟的註釋了。這個註釋的內容是有意義的,而不是傳
統意義上的註釋做用。這個例子裏用的是一個塊註釋,實際上用行註釋也是沒問題的,只要是
貼在import語句以前便可。好比下面也是正確的Cgo寫法:
// #include <stdio.h>
// #include <stdlib.h>
import "C"

9.2.1 類型映射
跨語言交互中,比較複雜的問題有兩個:類型映射以及跨越調用邊界傳遞指針所帶來的
象生命週期和內存管理的問題。好比Go語言中的string類型須要跟C語言中的字符數組進行對
應,而且要保證映射到C語言的對象的生命週期足夠長,以免在C語言執行過程當中該對象就已
經被垃圾回收。這一節咱們先談類型映射的問題。




9.2.2 字符串映射
由於Go語言中有明確的string原生類型,而C語言中用字符數組表示,二者之間的轉換是
一 個 必 須 考 慮 的 問 題 。 Cgo 提 供 了 一 系 列 函 數 來 提 供 支 持 : C.CString 、 C.GoString 和
C.GoStringN。須要注意的是,每次轉換都將致使一次內存複製,所以字符串內容實際上是不可
修改的(實際上,Go語言的string也不容許對其中的內容進行修改

因爲C.CString的內存管理方式與Go語言自身的內存管理方式不兼容,咱們設法期待Go語
言能夠幫咱們作垃圾收集,所以在使用完後必須顯式釋放調用C.CString所生成的內存塊,
不然將致使嚴重的內存泄露。結合咱們以前已經學過的defer用法,全部用到C.CString的代碼大體
均可以寫成以下的風格:
var gostr string
// ... 初始化gostr
cstr := C.CString(gostr)
defer C.free(unsafe.Pointer(cstr))
// 接下來大膽地使用cstr吧,由於保證能夠被釋放掉了
// C.sprintf(cstr, "content is: %d", 123)

9.2.3 C程序
在9.2節開頭的示例中,塊註釋中只寫了一條include語句,其實在這個塊註釋中,能夠寫
任意合法的C源代碼,而Cgo都會進行相應的處理並生成對應的Go代碼。代碼清單9-3是一個稍微
複雜一些的例子。
package hello
/*
#include <stdio.h>
void hello() {
printf("Hello, Cgo! -- From C world.\n");
}
*/
import "C"
func Hello() int {
return int(C.hello())
}
這個塊註釋裏就直接寫了個C函數,它使用C標準庫裏的printf()打印了一句話。
還有另一個問題,那就是若是這裏的C代碼須要依賴一個非C標準庫的第三方庫,怎麼辦
呢?若是不解決的話必然會有連接時錯誤。Cgo提供了#cgo這樣的僞C文法,讓開發者有機會指
定依賴的第三方庫和編譯選項。
下面的例子示範了#cgo的第一種用法:
// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo linux CFLAGS: -DLINUX=1
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"
這個例子示範瞭如何使用CFLAGS來傳入編譯選項,使用LDFLAGS來傳入連接選項。#cgo還有另
外一種更簡便一些的用法,以下所示:

// #cgo pkg-config: png cairo
// #include <png.h>
import "C"


9.2.5 編譯Cgo
編譯Cgo代碼很是容易,咱們不須要作任何特殊的處理。Go安裝後,會自帶一個cgo命令行
工具,它用於處理全部帶有Cgo代碼的Go文件,生成Go語言版本的調用封裝代碼。而Go工具集
對cgo命令行工具再次進行了良好的封裝,使構建過程可以自動識別和處理帶有Cgo代碼的Go源
代碼文件,徹底不給用戶增長額外的工做負擔。

9.3 連接符號

。。。。
因爲 Go 語言並沒有重載,故此語言的「連接符號」由以下信息構成。
 Package。Package 名能夠是多層,例如A/B/C。
 ClassType。 很特別的是,Go 語言中 ClassType 能夠是指針,也能夠不是。
 Method。
其「連接符號」的組成規則以下:
 Package.Method
 Package.ClassType·Method
這樣說可能比較抽象,下面舉個實際的例子。假設在 qbox.us/mockfs 模塊中,有以下幾個
函數:
func New(cfg Config) *MockFS
func (fs *MockFS) Mkdir(dir string) (code int, err error)
func (fs MockFS) Foo(bar Bar)
它們的連接符號分別爲:
qbox.us/mockfs.New
qbox.us/mockfs.*MockFS·Mkdir
qbox.us/mockfs.MockFS·Foo



9.4
goroutine 機理
。。。。。。
9.4.1 協程
協程,也有人稱之爲輕量級線程,具有如下幾個特色。
 可以在單一的系統線程中模擬多個任務的併發執行
 在一個特定的時間,只有一個任務在運行,即並不是真正地並行。
 被動的任務調度方式,即任務沒有主動搶佔時間片的說法。當一個任務正在執行時,外
部沒有辦法停止它。要進行任務切換,只能經過由該任務自身調用yield()來主動出讓
CPU使用權
每一個協程都有本身的堆棧和局部變量
每一個協程都包含3種運行狀態:掛起、運行和中止。中止一般表示該協程已經執行完成(包
括遇到問題後明確退出執行的狀況),掛起則表示該協程還沒有執行完成,但出讓了時間片,之後
有機會時會由調度器繼續執行。

9.4.2 協程的C語言實現
爲了更好地剖析協程的運行原理,咱們在本節中將引入Go語言的做者之一拉斯·考克斯
(Russ Cox)在Go語言出世以前所設計實現的一個輕量級協程庫libtask,這個庫的下載地址
爲http://swtch.com/libtask/,讀者能夠自行到該頁面下載完整的源代碼。這個庫的做者使用的是
很是開放的受權協議,所以讀者能夠隨意修改和使用這些代碼,但必須保持該份代碼所附帶的
版權聲明。
雖然咱們沒有具體地比對goroutine實現代碼和libtask的直接關係,但咱們有足夠充分的理
由相信goroutine和用於goroutine之間通訊的channel都是參照libtask庫實現的(甚至可能直接使
用這個庫)
。至於go關鍵字等Go語言特性,咱們均可以將其認爲只是爲了便於開發者使用而設計
的語法糖。
本節咱們將對這個代碼庫作一次結構化的閱讀,並在必要的地方貼出一些關鍵的代碼段。相
信讀者在閱讀完本節後,對於協程的原理會有比較全面的理解。理解了協程的概念,對於正確使
用Go語言的goroutine以及分析使用goroutine時遇到的各類問題都會大有幫助。


9.4.3 協程庫概述































 

nn

var book Book而後調用 json.Unmarshal() 函數, []byte 類型的JSON數據做爲第一個參數傳入, book將將實例變量的指針做爲第二個參數傳入:

相關文章
相關標籤/搜索