Golang 新手要注意的陷阱和常見(jiàn)錯(cuò)誤
Go是一門簡(jiǎn)單有趣的語(yǔ)言,但與其他語(yǔ)言類似,它會(huì)有一些技巧彪薛。。怠蹂。這些技巧的絕大部分并不是Go的缺陷造成的陪汽。如果你以前使用的是其他語(yǔ)言,那么這其中的有些錯(cuò)誤就是很自然的陷阱褥蚯。其它的是由錯(cuò)誤的假設(shè)和缺少細(xì)節(jié)造成的。
如果你花時(shí)間學(xué)習(xí)這門語(yǔ)言况增,閱讀官方說(shuō)明赞庶、wiki、郵件列表討論澳骤、大量的優(yōu)秀博文和Rob Pike的展示歧强,以及源代碼,這些技巧中的絕大多數(shù)都是顯而易見(jiàn)的为肮。盡管不是每個(gè)人都是以這種方式開始學(xué)習(xí)的摊册,但也沒(méi)關(guān)系。如果你是Go語(yǔ)言新人颊艳,那么這里的信息將會(huì)節(jié)約你大量的調(diào)試代碼的時(shí)間茅特。
初級(jí)
開大括號(hào)不能放在單獨(dú)的一行
在大多數(shù)其他使用大括號(hào)的語(yǔ)言中,你需要選擇放置它們的位置棋枕。Go的方式不同白修。你可以為此感謝下自動(dòng)分號(hào)的注入(沒(méi)有預(yù)讀)。是的重斑,Go中也是有分號(hào)的:-)
失敗的例子:
package main
import "fmt"
func main()
{ //error, can't have the opening brace on a separate line
fmt.Println("hello there!")
}
編譯錯(cuò)誤:
/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {
有效的例子:
package main
import "fmt"
func main() {
fmt.Println("works!")
}
未使用的變量
如果你有未使用的變量兵睛,代碼將編譯失敗。當(dāng)然也有例外窥浪。在函數(shù)內(nèi)一定要使用聲明的變量祖很,但未使用的全局變量是沒(méi)問(wèn)題的。
如果你給未使用的變量分配了一個(gè)新的值漾脂,代碼還是會(huì)編譯失敗假颇。你需要在某個(gè)地方使用這個(gè)變量,才能讓編譯器愉快的編譯符相。
Fails:
package main
var gvar int //not an error
func main() {
var one int //error, unused variable
two := 2 //error, unused variable
var three int //error, even though it's assigned 3 on the next line
three = 3
}
Compile Errors:
/tmp/sandbox473116179/main.go:6: one declared and not used
/tmp/sandbox473116179/main.go:7: two declared and not used
/tmp/sandbox473116179/main.go:8: three declared and not used
Works:
package main
import "fmt"
func main() {
var one int
_ = one
two := 2
fmt.Println(two)
var three int
three = 3
one = three
var four int
four = four
}
另一個(gè)選擇是注釋掉或者移除未使用的變量 :-)
未使用的Imports
如果你引入一個(gè)包拆融,而沒(méi)有使用其中的任何函數(shù)蠢琳、接口、結(jié)構(gòu)體或者變量的話镜豹,代碼將會(huì)編譯失敗傲须。
你可以使用goimports來(lái)增加引入或者移除未使用的引用:
$ go get golang.org/x/tools/cmd/goimports
如果你真的需要引入的包,你可以添加一個(gè)下劃線標(biāo)記符趟脂,_泰讽,來(lái)作為這個(gè)包的名字,從而避免編譯失敗昔期。下滑線標(biāo)記符用于引入已卸,但不使用。
Fails:
package main
import (
"fmt"
"log"
"time"
)
func main() {
}
Compile Errors:
/tmp/sandbox627475386/main.go:4: imported and not used: "fmt"
/tmp/sandbox627475386/main.go:5: imported and not used: "log"
/tmp/sandbox627475386/main.go:6: imported and not used: "time"
Works:
package main
import (
_ "fmt"
"log"
"time"
)
var _ = log.Println
func main() {
_ = time.Now
}
另一個(gè)選擇是移除或者注釋掉未使用的imports :-)
簡(jiǎn)式的變量聲明僅可以在函數(shù)內(nèi)部使用
Fails:
package main
myvar := 1 //error
func main() {
}
Compile Error:
/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body
Works:
package main
var myvar = 1
func main() {
}
使用簡(jiǎn)式聲明重復(fù)聲明變量
你不能在一個(gè)單獨(dú)的聲明中重復(fù)聲明一個(gè)變量硼一,但在多變量聲明中這是允許的累澡,其中至少要有一個(gè)新的聲明變量。
重復(fù)變量需要在相同的代碼塊內(nèi)般贼,否則你將得到一個(gè)隱藏變量愧哟。
Fails:
package main
func main() {
one := 0
one := 1 //error
}
Compile Error:
/tmp/sandbox706333626/main.go:5: no new variables on left side of :=
Works:
package main
func main() {
one := 0
one, two := 1,2
one,two = two,one
}
偶然的變量隱藏Accidental Variable Shadowing
短式變量聲明的語(yǔ)法如此的方便(尤其對(duì)于那些使用過(guò)動(dòng)態(tài)語(yǔ)言的開發(fā)者而言),很容易讓人把它當(dāng)成一個(gè)正常的分配操作哼蛆。如果你在一個(gè)新的代碼塊中犯了這個(gè)錯(cuò)誤蕊梧,將不會(huì)出現(xiàn)編譯錯(cuò)誤,但你的應(yīng)用將不會(huì)做你所期望的事情腮介。
package main
import "fmt"
func main() {
x := 1
fmt.Println(x) //prints 1
{
fmt.Println(x) //prints 1
x := 2
fmt.Println(x) //prints 2
}
fmt.Println(x) //prints 1 (bad if you need 2)
}
即使對(duì)于經(jīng)驗(yàn)豐富的Go開發(fā)者而言肥矢,這也是一個(gè)非常常見(jiàn)的陷阱。這個(gè)坑很容易挖叠洗,但又很難發(fā)現(xiàn)甘改。
你可以使用 vet命令來(lái)發(fā)現(xiàn)一些這樣的問(wèn)題。 默認(rèn)情況下灭抑, vet
不會(huì)執(zhí)行這樣的檢查楼誓,你需要設(shè)置-shadow
參數(shù):
go tool vet -shadow your_file.go
。
不使用顯式類型名挥,無(wú)法使用“nil”來(lái)初始化變量
nil
標(biāo)志符用于表示interface疟羹、函數(shù)、maps禀倔、slices和channels的“零值”榄融。如果你不指定變量的類型,編譯器將無(wú)法編譯你的代碼救湖,因?yàn)樗虏怀鼍唧w的類型愧杯。
Fails:
package main
func main() {
var x = nil //error
_ = x
}
Compile Error:
/tmp/sandbox188239583/main.go:4: use of untyped nil
Works:
package main
func main() {
var x interface{} = nil
_ = x
}
使用“nil” Slices and Maps
在一個(gè)nil
的slice中添加元素是沒(méi)問(wèn)題的,但對(duì)一個(gè)map做同樣的事將會(huì)生成一個(gè)運(yùn)行時(shí)的panic鞋既。
Works:
package main
func main() {
var s []int
s = append(s,1)
}
Fails:
package main
func main() {
var m map[string]int
m["one"] = 1 //error
}
Map的容量
你可以在map創(chuàng)建時(shí)指定它的容量力九,但你無(wú)法在map上使用cap()函數(shù)耍铜。
Fails:
package main
func main() {
m := make(map[string]int,99)
cap(m) //error
}
Compile Error:
/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap
字符串不會(huì)為nil
這對(duì)于經(jīng)常使用nil
分配字符串變量的開發(fā)者而言是個(gè)需要注意的地方。
Fails:
package main
func main() {
var x string = nil //error
if x == nil { //error
x = "default"
}
}
Compile Errors:
/tmp/sandbox630560459/main.go:4: cannot use nil as type string in assignment /tmp/sandbox630560459/main.go:6: invalid operation: x == nil (mismatched types string and nil)
Works:
package main
func main() {
var x string //defaults to "" (zero value)
if x == "" {
x = "default"
}
}
Array函數(shù)的參數(shù)
如果你是一個(gè)C或則C++開發(fā)者跌前,那么數(shù)組對(duì)你而言就是指針棕兼。當(dāng)你向函數(shù)中傳遞數(shù)組時(shí),函數(shù)會(huì)參照相同的內(nèi)存區(qū)域抵乓,這樣它們就可以修改原始的數(shù)據(jù)伴挚。Go中的數(shù)組是數(shù)值,因此當(dāng)你向函數(shù)中傳遞數(shù)組時(shí)灾炭,函數(shù)會(huì)得到原始數(shù)組數(shù)據(jù)的一份復(fù)制茎芋。如果你打算更新數(shù)組的數(shù)據(jù),這將會(huì)是個(gè)問(wèn)題蜈出。
package main
import "fmt"
func main() {
x := [3]int{1,2,3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr) //prints [7 2 3]
}(x)
fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])
}
如果你需要更新原始數(shù)組的數(shù)據(jù)田弥,你可以使用數(shù)組指針類型。
package main
import "fmt"
func main() {
x := [3]int{1,2,3}
func(arr *[3]int) {
(*arr)[0] = 7
fmt.Println(arr) //prints &[7 2 3]
}(&x)
fmt.Println(x) //prints [7 2 3]
}
另一個(gè)選擇是使用slice铡原。即使你的函數(shù)得到了slice變量的一份拷貝皱蹦,它依舊會(huì)參照原始的數(shù)據(jù)。
package main
import "fmt"
func main() {
x := []int{1,2,3}
func(arr []int) {
arr[0] = 7
fmt.Println(arr) //prints [7 2 3]
}(x)
fmt.Println(x) //prints [7 2 3]
}
在Slice和Array使用“range”語(yǔ)句時(shí)的出現(xiàn)的不希望得到的值
如果你在其他的語(yǔ)言中使用“for-in”或者“foreach”語(yǔ)句時(shí)會(huì)發(fā)生這種情況眷蜈。Go中的“range”語(yǔ)法不太一樣。它會(huì)得到兩個(gè)值:第一個(gè)值是元素的索引沈自,而另一個(gè)值是元素的數(shù)據(jù)酌儒。
Bad:
package main
import "fmt"
func main() {
x := []string{"a","b","c"}
for v := range x {
fmt.Println(v) //prints 0, 1, 2
}
}
Good:
package main
import "fmt"
func main() {
x := []string{"a","b","c"}
for _, v := range x {
fmt.Println(v) //prints a, b, c
}
}
Slices和Arrays是一維的
看起來(lái)Go好像支持多維的Array和Slice,但不是這樣的枯途。盡管可以創(chuàng)建數(shù)組的數(shù)組或者切片的切片忌怎。對(duì)于依賴于動(dòng)態(tài)多維數(shù)組的數(shù)值計(jì)算應(yīng)用而言,Go在性能和復(fù)雜度上還相距甚遠(yuǎn)酪夷。
你可以使用純一維數(shù)組榴啸、“獨(dú)立”切片的切片,“共享數(shù)據(jù)”切片的切片來(lái)構(gòu)建動(dòng)態(tài)的多維數(shù)組晚岭。
如果你使用純一維的數(shù)組鸥印,你需要處理索引、邊界檢查坦报、當(dāng)數(shù)組需要變大時(shí)的內(nèi)存重新分配库说。
使用“獨(dú)立”slice來(lái)創(chuàng)建一個(gè)動(dòng)態(tài)的多維數(shù)組需要兩步。首先片择,你需要?jiǎng)?chuàng)建一個(gè)外部的slice潜的。然后,你需要分配每個(gè)內(nèi)部的slice字管。內(nèi)部的slice相互之間獨(dú)立啰挪。你可以增加減少它們信不,而不會(huì)影響其他內(nèi)部的slice。
package main
func main() {
x := 2
y := 4
table := make([][]int,x)
for i:= range table {
table[i] = make([]int,y)
}
}
使用“共享數(shù)據(jù)”slice的slice來(lái)創(chuàng)建一個(gè)動(dòng)態(tài)的多維數(shù)組需要三步亡呵。首先抽活,你需要?jiǎng)?chuàng)建一個(gè)用于存放原始數(shù)據(jù)的數(shù)據(jù)“容器”。然后政己,你再創(chuàng)建外部的slice酌壕。最后,通過(guò)重新切片原始數(shù)據(jù)slice來(lái)初始化各個(gè)內(nèi)部的slice歇由。
package main
import "fmt"
func main() {
h, w := 2, 4
raw := make([]int,h*w)
for i := range raw {
raw[i] = i
}
fmt.Println(raw,&raw[4])
//prints: [0 1 2 3 4 5 6 7] <ptr_addr_x>
table := make([][]int,h)
for i:= range table {
table[i] = raw[i*w:i*w + w]
}
fmt.Println(table,&table[1][0])
//prints: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x>
}
關(guān)于多維array和slice已經(jīng)有了專門申請(qǐng)卵牍,但現(xiàn)在看起來(lái)這是個(gè)低優(yōu)先級(jí)的特性。
訪問(wèn)不存在的Map Keys
這對(duì)于那些希望得到“nil”標(biāo)示符的開發(fā)者而言是個(gè)技巧(和其他語(yǔ)言中做的一樣)沦泌。如果對(duì)應(yīng)的數(shù)據(jù)類型的“零值”是“nil”糊昙,那返回的值將會(huì)是“nil”,但對(duì)于其他的數(shù)據(jù)類型是不一樣的谢谦。檢測(cè)對(duì)應(yīng)的“零值”可以用于確定map中的記錄是否存在释牺,但這并不總是可信(比如,如果在二值的map中“零值”是false回挽,這時(shí)你要怎么做)没咙。檢測(cè)給定map中的記錄是否存在的最可信的方法是,通過(guò)map的訪問(wèn)操作千劈,檢查第二個(gè)返回的值祭刚。
Bad:
package main
import "fmt"
func main() {
x := map[string]string{"one":"a","two":"","three":"c"}
if v := x["two"]; v == "" { //incorrect
fmt.Println("no entry")
}
}
Good:
package main
import "fmt"
func main() {
x := map[string]string{"one":"a","two":"","three":"c"}
if _,ok := x["two"]; !ok {
fmt.Println("no entry")
}
}
Strings無(wú)法修改
嘗試使用索引操作來(lái)更新字符串變量中的單個(gè)字符將會(huì)失敗。string是只讀的byte slice(和一些額外的屬性)墙牌。如果你確實(shí)需要更新一個(gè)字符串涡驮,那么使用byte slice,并在需要時(shí)把它轉(zhuǎn)換為string類型喜滨。
Fails:
package main
import "fmt"
func main() {
x := "text"
x[0] = 'T'
fmt.Println(x)
}
Compile Error:
/tmp/sandbox305565531/main.go:7: cannot assign to x[0]
Works:
package main
import "fmt"
func main() {
x := "text"
xbytes := []byte(x)
xbytes[0] = 'T'
fmt.Println(string(xbytes)) //prints Text
}
需要注意的是:這并不是在文字string中更新字符的正確方式捉捅,因?yàn)榻o定的字符可能會(huì)存儲(chǔ)在多個(gè)byte中。如果你確實(shí)需要更新一個(gè)文字string虽风,先把它轉(zhuǎn)換為一個(gè)rune slice棒口。即使使用rune slice,單個(gè)字符也可能會(huì)占據(jù)多個(gè)rune辜膝,比如當(dāng)你的字符有特定的重音符號(hào)時(shí)就是這種情況陌凳。這種復(fù)雜又模糊的“字符”本質(zhì)是Go字符串使用byte序列表示的原因。
String和Byte Slice之間的轉(zhuǎn)換
當(dāng)你把一個(gè)字符串轉(zhuǎn)換為一個(gè)byte slice
(或者反之)時(shí)内舟,你就得到了一個(gè)原始數(shù)據(jù)的完整拷貝合敦。這和其他語(yǔ)言中cast操作不同,也和新的slice
變量指向原始byte
slice使用的相同數(shù)組時(shí)的重新slice操作不同验游。
Go在[]byte
到string
和string
到[]byte
的轉(zhuǎn)換中確實(shí)使用了一些優(yōu)化來(lái)避免額外的分配(在todo列表中有更多的優(yōu)化)充岛。
第一個(gè)優(yōu)化避免了當(dāng)[]byte
keys用于在map[string]
集合中查詢時(shí)的額外分配:m[string(key)]
保檐。
第二個(gè)優(yōu)化避免了字符串轉(zhuǎn)換為[]byte
后在for range
語(yǔ)句中的額外分配:for i,v := range []byte(str) {...}
。
String和索引操作
字符串上的索引操作返回一個(gè)byte值崔梗,而不是一個(gè)字符(和其他語(yǔ)言中的做法一樣)夜只。
package main
import "fmt"
func main() {
x := "text"
fmt.Println(x[0]) //print 116
fmt.Printf("%T",x[0]) //prints uint8
}
如果你需要訪問(wèn)特定的字符串“字符”(unicode編碼的points/runes),使用for range蒜魄。官方的“unicode/utf8”包和實(shí)驗(yàn)中的utf8string包(golang.org/x/exp/utf8string)也可以用扔亥。utf8string包中包含了一個(gè)很方便的At()方法。把字符串轉(zhuǎn)換為rune的切片也是一個(gè)選項(xiàng)谈为。
字符串不總是UTF8文本
字符串的值不需要是UTF8的文本旅挤。它們可以包含任意的字節(jié)。只有在string literal使用時(shí)伞鲫,字符串才會(huì)是UTF8粘茄。即使之后它們可以使用轉(zhuǎn)義序列來(lái)包含其他的數(shù)據(jù)。
為了知道字符串是否是UTF8秕脓,你可以使用“unicode/utf8”包中的ValidString()函數(shù)柒瓣。
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data1 := "ABC"
fmt.Println(utf8.ValidString(data1)) //prints: true
data2 := "A\xfeC"
fmt.Println(utf8.ValidString(data2)) //prints: false
}
字符串的長(zhǎng)度
讓我們假設(shè)你是Python開發(fā)者,你有下面這段代碼:
data = u'?'
print(len(data)) #prints: 1
當(dāng)把它轉(zhuǎn)換為Go代碼時(shí)吠架,你可能會(huì)大吃一驚芙贫。
package main
import "fmt"
func main() {
data := "?"
fmt.Println(len(data)) //prints: 3
}
內(nèi)建的len()
函數(shù)返回byte的數(shù)量,而不是像Python中計(jì)算好的unicode字符串中字符的數(shù)量傍药。
要在Go中得到相同的結(jié)果磺平,可以使用“unicode/utf8”包中的RuneCountInString()
函數(shù)。
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data := "?"
fmt.Println(utf8.RuneCountInString(data)) //prints: 1
}
理論上說(shuō)RuneCountInString()
函數(shù)并不返回字符的數(shù)量怔檩,因?yàn)閱蝹€(gè)字符可能占用多個(gè)rune。
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data := "e?"
fmt.Println(len(data)) //prints: 3
fmt.Println(utf8.RuneCountInString(data)) //prints: 2
}
在多行的Slice蓄诽、Array和Map語(yǔ)句中遺漏逗號(hào)
Fails:
package main
func main() {
x := []int{
1,
2 //error
}
_ = x
}
Compile Errors:
/tmp/sandbox367520156/main.go:6: syntax error: need trailing comma before newline in composite literal /tmp/sandbox367520156/main.go:8: non-declaration statement outside function body /tmp/sandbox367520156/main.go:9: syntax error: unexpected }
Works:
package main
func main() {
x := []int{
1,
2,
}
x = x
y := []int{3,4,} //no error
y = y
}
當(dāng)你把聲明折疊到單行時(shí)薛训,如果你沒(méi)加末尾的逗號(hào),你將不會(huì)得到編譯錯(cuò)誤仑氛。
log.Fatal和log.Panic不僅僅是Log
Logging庫(kù)一般提供不同的log等級(jí)乙埃。與這些logging庫(kù)不同,Go中l(wèi)og包在你調(diào)用它的Fatal*()
和Panic*()
函數(shù)時(shí)锯岖,可以做的不僅僅是log介袜。當(dāng)你的應(yīng)用調(diào)用這些函數(shù)時(shí),Go也將會(huì)終止應(yīng)用 :-)
package main
import "log"
func main() {
log.Fatalln("Fatal Level: log entry") //app exits here
log.Println("Normal Level: log entry")
}
內(nèi)建的數(shù)據(jù)結(jié)構(gòu)操作不是同步的
即使Go本身有很多特性來(lái)支持并發(fā)出吹,并發(fā)安全的數(shù)據(jù)集合并不是其中之一 :-)確保數(shù)據(jù)集合以原子的方式更新是你的職責(zé)遇伞。Goroutines和channels是實(shí)現(xiàn)這些原子操作的推薦方式,但你也可以使用“sync”包捶牢,如果它對(duì)你的應(yīng)用有意義的話鸠珠。
String在“range”語(yǔ)句中的迭代值
索引值(“range”操作返回的第一個(gè)值)是返回的第二個(gè)值的當(dāng)前“字符”(unicode編碼的point/rune)的第一個(gè)byte的索引巍耗。它不是當(dāng)前“字符”的索引,這與其他語(yǔ)言不同渐排。注意真實(shí)的字符可能會(huì)由多個(gè)rune表示炬太。如果你需要處理字符,確保你使用了“norm”包(golang.org/x/text/unicode/norm)驯耻。
string變量的for range
語(yǔ)句將會(huì)嘗試把數(shù)據(jù)翻譯為UTF8文本亲族。對(duì)于它無(wú)法理解的任何byte序列,它將返回0xfffd runes(即unicode替換字符)可缚,而不是真實(shí)的數(shù)據(jù)霎迫。如果你任意(非UTF8文本)的數(shù)據(jù)保存在string變量中,確保把它們轉(zhuǎn)換為byte slice城看,以得到所有保存的數(shù)據(jù)女气。
package main
import "fmt"
func main() {
data := "A\xfe\x02\xff\x04"
for _,v := range data {
fmt.Printf("%#x ",v)
}
//prints: 0x41 0xfffd 0x2 0xfffd 0x4 (not ok)
fmt.Println()
for _,v := range []byte(data) {
fmt.Printf("%#x ",v)
}
//prints: 0x41 0xfe 0x2 0xff 0x4 (good)
}
對(duì)Map使用“for range”語(yǔ)句迭代
如果你希望以某個(gè)順序(比如,按key值排序)的方式得到元素测柠,就需要這個(gè)技巧炼鞠。每次的map迭代將會(huì)生成不同的結(jié)果。Go的runtime有心嘗試隨機(jī)化迭代順序轰胁,但并不總會(huì)成功谒主,這樣你可能得到一些相同的map迭代結(jié)果。所以如果連續(xù)看到5個(gè)相同的迭代結(jié)果赃阀,不要驚訝霎肯。
package main
import "fmt"
func main() {
m := map[string]int{"one":1,"two":2,"three":3,"four":4}
for k,v := range m {
fmt.Println(k,v)
}
}
而且如果你使用Go的游樂(lè)場(chǎng)(https://play.golang.org/),你將總會(huì)得到同樣的結(jié)果榛斯,因?yàn)槌悄阈薷拇a观游,否則它不會(huì)重新編譯代碼。
"switch"聲明中的失效行為
在“switch”聲明語(yǔ)句中的“case”語(yǔ)句塊在默認(rèn)情況下會(huì)break驮俗。這和其他語(yǔ)言中的進(jìn)入下一個(gè)“next”代碼塊的默認(rèn)行為不同懂缕。
package main
import "fmt"
func main() {
isSpace := func(ch byte) bool {
switch(ch) {
case ' ': //error
case '\t':
return true
}
return false
}
fmt.Println(isSpace('\t')) //prints true (ok)
fmt.Println(isSpace(' ')) //prints false (not ok)
}
你可以通過(guò)在每個(gè)“case”塊的結(jié)尾使用“fallthrough”,來(lái)強(qiáng)制“case”代碼塊進(jìn)入王凑。你也可以重寫switch語(yǔ)句搪柑,來(lái)使用“case”塊中的表達(dá)式列表。
package main
import "fmt"
func main() {
isSpace := func(ch byte) bool {
switch(ch) {
case ' ', '\t':
return true
}
return false
}
fmt.Println(isSpace('\t')) //prints true (ok)
fmt.Println(isSpace(' ')) //prints true (ok)
}
自增和自減
許多語(yǔ)言都有自增和自減操作索烹。不像其他語(yǔ)言工碾,Go不支持前置版本的操作。你也無(wú)法在表達(dá)式中使用這兩個(gè)操作符百姓。
Fails:
package main
import "fmt"
func main() {
data := []int{1,2,3}
i := 0
++i //error
fmt.Println(data[i++]) //error
}
Compile Errors:
/tmp/sandbox101231828/main.go:8: syntax error: unexpected ++ /tmp/sandbox101231828/main.go:9: syntax error: unexpected ++, expecting :
Works:
package main
import "fmt"
func main() {
data := []int{1,2,3}
i := 0
i++
fmt.Println(data[i])
}
按位NOT操作
許多語(yǔ)言使用 ~
作為一元的NOT操作符(即按位補(bǔ)足)渊额,但Go為了這個(gè)重用了XOR操作符(^)。
Fails:
package main
import "fmt"
func main() {
fmt.Println(~2) //error
}
Compile Error:
/tmp/sandbox965529189/main.go:6: the bitwise complement operator is ^
Works:
package main
import "fmt"
func main() {
var d uint8 = 2
fmt.Printf("%08b\n",^d)
}
Go依舊使用^
作為XOR的操作符,這可能會(huì)讓一些人迷惑端圈。
如果你愿意焦读,你可以使用一個(gè)二元的XOR操作(如, 0x02 XOR 0xff)來(lái)表示一個(gè)一元的NOT操作(如舱权,NOT 0x02)矗晃。這可以解釋為什么^
被重用來(lái)表示一元的NOT操作。
Go也有特殊的‘AND NOT’按位操作(&^
)宴倍,這也讓NOT操作更加的讓人迷惑张症。這看起來(lái)需要特殊的特性/hack來(lái)支持 A AND (NOT B)
,而無(wú)需括號(hào)鸵贬。
package main
import "fmt"
func main() {
var a uint8 = 0x82
var b uint8 = 0x02
fmt.Printf("%08b [A]\n",a)
fmt.Printf("%08b [B]\n",b)
fmt.Printf("%08b (NOT B)\n",^b)
fmt.Printf("%08b ^ %08b = %08b [B XOR 0xff]\n",b,0xff,b ^ 0xff)
fmt.Printf("%08b ^ %08b = %08b [A XOR B]\n",a,b,a ^ b)
fmt.Printf("%08b & %08b = %08b [A AND B]\n",a,b,a & b)
fmt.Printf("%08b &^%08b = %08b [A 'AND NOT' B]\n",a,b,a &^ b)
fmt.Printf("%08b&(^%08b)= %08b [A AND (NOT B)]\n",a,b,a & (^b))
}
操作優(yōu)先級(jí)的差異
除了”bit clear“操作(&^
)俗他,Go也一個(gè)與許多其他語(yǔ)言共享的標(biāo)準(zhǔn)操作符的集合。盡管操作優(yōu)先級(jí)并不總是一樣阔逼。
package main
import "fmt"
func main() {
fmt.Printf("0x2 & 0x2 + 0x4 -> %#x\n",0x2 & 0x2 + 0x4)
//prints: 0x2 & 0x2 + 0x4 -> 0x6
//Go: (0x2 & 0x2) + 0x4
//C++: 0x2 & (0x2 + 0x4) -> 0x2
fmt.Printf("0x2 + 0x2 << 0x1 -> %#x\n",0x2 + 0x2 << 0x1)
//prints: 0x2 + 0x2 << 0x1 -> 0x6
//Go: 0x2 + (0x2 << 0x1)
//C++: (0x2 + 0x2) << 0x1 -> 0x8
fmt.Printf("0xf | 0x2 ^ 0x2 -> %#x\n",0xf | 0x2 ^ 0x2)
//prints: 0xf | 0x2 ^ 0x2 -> 0xd
//Go: (0xf | 0x2) ^ 0x2
//C++: 0xf | (0x2 ^ 0x2) -> 0xf
}
未導(dǎo)出的結(jié)構(gòu)體不會(huì)被編碼
以小寫字母開頭的結(jié)構(gòu)體將不會(huì)被(json兆衅、xml、gob等)編碼嗜浮,因此當(dāng)你編碼這些未導(dǎo)出的結(jié)構(gòu)體時(shí)羡亩,你將會(huì)得到零值。
Fails:
package main
import (
"fmt"
"encoding/json"
)
type MyData struct {
One int
two string
}
func main() {
in := MyData{1,"two"}
fmt.Printf("%#v\n",in) //prints main.MyData{One:1, two:"two"}
encoded,_ := json.Marshal(in)
fmt.Println(string(encoded)) //prints {"One":1}
var out MyData
json.Unmarshal(encoded,&out)
fmt.Printf("%#v\n",out) //prints main.MyData{One:1, two:""}
}
有活動(dòng)的Goroutines下的應(yīng)用退出
應(yīng)用將不會(huì)等待所有的goroutines完成危融。這對(duì)于初學(xué)者而言是個(gè)很常見(jiàn)的錯(cuò)誤畏铆。每個(gè)人都是以某個(gè)程度開始,因此如果犯了初學(xué)者的錯(cuò)誤也沒(méi)神馬好丟臉的 :-)
package main
import (
"fmt"
"time"
)
func main() {
workerCount := 2
for i := 0; i < workerCount; i++ {
go doit(i)
}
time.Sleep(1 * time.Second)
fmt.Println("all done!")
}
func doit(workerId int) {
fmt.Printf("[%v] is running\n",workerId)
time.Sleep(3 * time.Second)
fmt.Printf("[%v] is done\n",workerId)
}
你將會(huì)看到:
[0] is running
[1] is running
all done!
一個(gè)最常見(jiàn)的解決方法是使用“WaitGroup”變量吉殃。它將會(huì)讓主goroutine等待所有的worker goroutine完成辞居。如果你的應(yīng)用有長(zhǎng)時(shí)運(yùn)行的消息處理循環(huán)的worker,你也將需要一個(gè)方法向這些goroutine發(fā)送信號(hào)蛋勺,讓它們退出瓦灶。你可以給各個(gè)worker發(fā)送一個(gè)“kill”消息。另一個(gè)選項(xiàng)是關(guān)閉一個(gè)所有worker都接收的channel抱完。這是一次向所有g(shù)oroutine發(fā)送信號(hào)的簡(jiǎn)單方式贼陶。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
done := make(chan struct{})
workerCount := 2
for i := 0; i < workerCount; i++ {
wg.Add(1)
go doit(i,done,wg)
}
close(done)
wg.Wait()
fmt.Println("all done!")
}
func doit(workerId int,done <-chan struct{},wg sync.WaitGroup) {
fmt.Printf("[%v] is running\n",workerId)
defer wg.Done()
<- done
fmt.Printf("[%v] is done\n",workerId)
}
如果你運(yùn)行這個(gè)應(yīng)用,你將會(huì)看到:
[0] is running
[0] is done
[1] is running
[1] is done
看起來(lái)所有的worker在主goroutine退出前都完成了乾蛤。棒每界!然而捅僵,你也將會(huì)看到這個(gè):
fatal error: all goroutines are asleep - deadlock!
這可不太好 :-) 發(fā)送了神馬家卖?為什么會(huì)出現(xiàn)死鎖?worker退出了庙楚,它們也執(zhí)行了wg.Done()
。應(yīng)用應(yīng)該沒(méi)問(wèn)題啊。
死鎖發(fā)生是因?yàn)楦鱾€(gè)worker都得到了原始的“WaitGroup”變量的一個(gè)拷貝檐涝。當(dāng)worker執(zhí)行wg.Done()
時(shí),并沒(méi)有在主goroutine上的“WaitGroup”變量上生效叁征。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
done := make(chan struct{})
wq := make(chan interface{})
workerCount := 2
for i := 0; i < workerCount; i++ {
wg.Add(1)
go doit(i,wq,done,&wg)
}
for i := 0; i < workerCount; i++ {
wq <- i
}
close(done)
wg.Wait()
fmt.Println("all done!")
}
func doit(workerId int, wq <-chan interface{},done <-chan struct{},wg *sync.WaitGroup) {
fmt.Printf("[%v] is running\n",workerId)
defer wg.Done()
for {
select {
case m := <- wq:
fmt.Printf("[%v] m => %v\n",workerId,m)
case <- done:
fmt.Printf("[%v] is done\n",workerId)
return
}
}
}
現(xiàn)在它會(huì)如預(yù)期般工作 :-)
向無(wú)緩存的Channel發(fā)送消息,只要目標(biāo)接收者準(zhǔn)備好就會(huì)立即返回
發(fā)送者將不會(huì)被阻塞逛薇,除非消息正在被接收者處理捺疼。根據(jù)你運(yùn)行代碼的機(jī)器的不同,接收者的goroutine可能會(huì)或者不會(huì)有足夠的時(shí)間永罚,在發(fā)送者繼續(xù)執(zhí)行前處理消息啤呼。
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
for m := range ch {
fmt.Println("processed:",m)
}
}()
ch <- "cmd.1"
ch <- "cmd.2" //won't be processed
}
向已關(guān)閉的Channel發(fā)送會(huì)引起Panic
從一個(gè)關(guān)閉的channel接收是安全的。在接收狀態(tài)下的ok
的返回值將被設(shè)置為false
呢袱,這意味著沒(méi)有數(shù)據(jù)被接收官扣。如果你從一個(gè)有緩存的channel接收,你將會(huì)首先得到緩存的數(shù)據(jù)羞福,一旦它為空惕蹄,返回的ok
值將變?yōu)?code>false。
向關(guān)閉的channel中發(fā)送數(shù)據(jù)會(huì)引起panic治专。這個(gè)行為有文檔說(shuō)明卖陵,但對(duì)于新的Go開發(fā)者的直覺(jué)不同,他們可能希望發(fā)送行為與接收行為很像看靠。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}
//get the first result
fmt.Println(<-ch)
close(ch) //not ok (you still have other senders)
//do other work
time.Sleep(2 * time.Second)
}
根據(jù)不同的應(yīng)用赶促,修復(fù)方法也將不同⌒妫可能是很小的代碼修改鸥滨,也可能需要修改應(yīng)用的設(shè)計(jì)。無(wú)論是哪種方法谤祖,你都需要確保你的應(yīng)用不會(huì)向關(guān)閉的channel中發(fā)送數(shù)據(jù)婿滓。
上面那個(gè)有bug的例子可以通過(guò)使用一個(gè)特殊的廢棄的channel來(lái)向剩余的worker發(fā)送不再需要它們的結(jié)果的信號(hào)來(lái)修復(fù)。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
done := make(chan struct{})
for i := 0; i < 3; i++ {
go func(idx int) {
select {
case ch <- (idx + 1) * 2: fmt.Println(idx,"sent result")
case <- done: fmt.Println(idx,"exiting")
}
}(i)
}
//get first result
fmt.Println("result:",<-ch)
close(done)
//do other work
time.Sleep(3 * time.Second)
}
使用"nil" Channels
在一個(gè)nil
的channel上發(fā)送和接收操作會(huì)被永久阻塞粥喜。這個(gè)行為有詳細(xì)的文檔解釋凸主,但它對(duì)于新的Go開發(fā)者而言是個(gè)驚喜。
package main
import (
"fmt"
"time"
)
func main() {
var ch chan int
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}
//get first result
fmt.Println("result:",<-ch)
//do other work
time.Sleep(2 * time.Second)
}
如果運(yùn)行代碼你將會(huì)看到一個(gè)runtime錯(cuò)誤:
fatal error: all goroutines are asleep - deadlock!
這個(gè)行為可以在select
聲明中用于動(dòng)態(tài)開啟和關(guān)閉case
代碼塊的方法额湘。
package main
import "fmt"
import "time"
func main() {
inch := make(chan int)
outch := make(chan int)
go func() {
var in <- chan int = inch
var out chan <- int
var val int
for {
select {
case out <- val:
out = nil
in = inch
case val = <- in:
out = outch
in = nil
}
}
}()
go func() {
for r := range outch {
fmt.Println("result:",r)
}
}()
time.Sleep(0)
inch <- 1
inch <- 2
time.Sleep(3 * time.Second)
}
傳值方法的接收者無(wú)法修改原有的值
方法的接收者就像常規(guī)的函數(shù)參數(shù)卿吐。如果聲明為值,那么你的函數(shù)/方法得到的是接收者參數(shù)的拷貝锋华。這意味著對(duì)接收者所做的修改將不會(huì)影響原有的值嗡官,除非接收者是一個(gè)map或者slice變量,而你更新了集合中的元素毯焕,或者你更新的域的接收者是指針衍腥。
package main
import "fmt"
type data struct {
num int
key *string
items map[string]bool
}
func (this *data) pmethod() {
this.num = 7
}
func (this data) vmethod() {
this.num = 8
*this.key = "v.key"
this.items["vmethod"] = true
}
func main() {
key := "key.1"
d := data{1,&key,make(map[string]bool)}
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
//prints num=1 key=key.1 items=map[]
d.pmethod()
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
//prints num=7 key=key.1 items=map[]
d.vmethod()
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
//prints num=7 key=v.key items=map[vmethod:true]
}
中級(jí)
關(guān)閉HTTP的響應(yīng)
當(dāng)你使用標(biāo)準(zhǔn)http庫(kù)發(fā)起請(qǐng)求時(shí),你得到一個(gè)http的響應(yīng)變量。如果你不讀取響應(yīng)主體婆咸,你依舊需要關(guān)閉它竹捉。注意對(duì)于空的響應(yīng)你也一定要這么做。對(duì)于新的Go開發(fā)者而言尚骄,這個(gè)很容易就會(huì)忘掉块差。
一些新的Go開發(fā)者確實(shí)嘗試關(guān)閉響應(yīng)主體,但他們?cè)阱e(cuò)誤的地方做倔丈。
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
defer resp.Body.Close()//not ok
if err != nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
這段代碼對(duì)于成功的請(qǐng)求沒(méi)問(wèn)題憾儒,但如果http的請(qǐng)求失敗,resp
變量可能會(huì)是nil
乃沙,這將導(dǎo)致一個(gè)runtime panic
起趾。
最常見(jiàn)的關(guān)閉響應(yīng)主體的方法是在http響應(yīng)的錯(cuò)誤檢查后調(diào)用defer
。
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()//ok, most of the time :-)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
大多數(shù)情況下警儒,當(dāng)你的http響應(yīng)失敗時(shí)训裆,resp
變量將為nil
,而err
變量將是non-nil
蜀铲。然而边琉,當(dāng)你得到一個(gè)重定向的錯(cuò)誤時(shí),兩個(gè)變量都將是non-nil
记劝。這意味著你最后依然會(huì)內(nèi)存泄露变姨。
通過(guò)在http響應(yīng)錯(cuò)誤處理中添加一個(gè)關(guān)閉non-nil
響應(yīng)主體的的調(diào)用來(lái)修復(fù)這個(gè)問(wèn)題。另一個(gè)方法是使用一個(gè)defer
調(diào)用來(lái)關(guān)閉所有失敗和成功的請(qǐng)求的響應(yīng)主體厌丑。
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
resp.Body.Close()
的原始實(shí)現(xiàn)也會(huì)讀取并丟棄剩余的響應(yīng)主體數(shù)據(jù)定欧。這確保了http的鏈接在keepalive http連接行為開啟的情況下,可以被另一個(gè)請(qǐng)求復(fù)用怒竿。最新的http客戶端的行為是不同的】仇現(xiàn)在讀取并丟棄剩余的響應(yīng)數(shù)據(jù)是你的職責(zé)。如果你不這么做耕驰,http的連接可能會(huì)關(guān)閉爷辱,而無(wú)法被重用。這個(gè)小技巧應(yīng)該會(huì)寫在Go 1.5的文檔中朦肘。
如果http連接的重用對(duì)你的應(yīng)用很重要饭弓,你可能需要在響應(yīng)處理邏輯的后面添加像下面的代碼:
_, err = io.Copy(ioutil.Discard, resp.Body)
如果你不立即讀取整個(gè)響應(yīng)將是必要的,這可能在你處理json API響應(yīng)時(shí)會(huì)發(fā)生:
json.NewDecoder(resp.Body).Decode(&data)
關(guān)閉HTTP的連接
一些HTTP服務(wù)器保持會(huì)保持一段時(shí)間的網(wǎng)絡(luò)連接(根據(jù)HTTP 1.1的說(shuō)明和服務(wù)器端的“keep-alive”配置)媒抠。默認(rèn)情況下弟断,標(biāo)準(zhǔn)http庫(kù)只在目標(biāo)HTTP服務(wù)器要求關(guān)閉時(shí)才會(huì)關(guān)閉網(wǎng)絡(luò)連接。這意味著你的應(yīng)用在某些條件下消耗完sockets/file的描述符领舰。
你可以通過(guò)設(shè)置請(qǐng)求變量中的Close
域的值為true
夫嗓,來(lái)讓http庫(kù)在請(qǐng)求完成時(shí)關(guān)閉連接。
另一個(gè)選項(xiàng)是添加一個(gè)Connection
的請(qǐng)求頭冲秽,并設(shè)置為close
舍咖。目標(biāo)HTTP服務(wù)器應(yīng)該也會(huì)響應(yīng)一個(gè)Connection: close
的頭。當(dāng)http庫(kù)看到這個(gè)響應(yīng)頭時(shí)锉桑,它也將會(huì)關(guān)閉連接排霉。
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
req, err := http.NewRequest("GET","http://golang.org",nil)
if err != nil {
fmt.Println(err)
return
}
req.Close = true
//or do this:
//req.Header.Add("Connection", "close")
resp, err := http.DefaultClient.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(string(body)))
}
你也可以取消http的全局連接復(fù)用。你將需要為此創(chuàng)建一個(gè)自定義的http傳輸配置民轴。
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
tr := &http.Transport{DisableKeepAlives: true}
client := &http.Client{Transport: tr}
resp, err := client.Get("http://golang.org")
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp.StatusCode)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(string(body)))
}
如果你向同一個(gè)HTTP服務(wù)器發(fā)送大量的請(qǐng)求攻柠,那么把保持網(wǎng)絡(luò)連接的打開是沒(méi)問(wèn)題的。然而后裸,如果你的應(yīng)用在短時(shí)間內(nèi)向大量不同的HTTP服務(wù)器發(fā)送一兩個(gè)請(qǐng)求瑰钮,那么在引用收到響應(yīng)后立刻關(guān)閉網(wǎng)絡(luò)連接是一個(gè)好主意。增加打開文件的限制數(shù)可能也是個(gè)好主意微驶。當(dāng)然浪谴,正確的選擇源自于應(yīng)用。
比較Structs, Arrays, Slices, and Maps
如果結(jié)構(gòu)體中的各個(gè)元素都可以用你可以使用等號(hào)來(lái)比較的話因苹,那就可以使用相號(hào), ==苟耻,來(lái)比較結(jié)構(gòu)體變量。
package main
import "fmt"
type data struct {
num int
fp float32
complex complex64
str string
char rune
yes bool
events <-chan string
handler interface{}
ref *byte
raw [10]byte
}
func main() {
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2:",v1 == v2) //prints: v1 == v2: true
}
如果結(jié)構(gòu)體中的元素?zé)o法比較扶檐,那使用等號(hào)將導(dǎo)致編譯錯(cuò)誤凶杖。注意數(shù)組僅在它們的數(shù)據(jù)元素可比較的情況下才可以比較。
package main
import "fmt"
type data struct {
num int //ok
checks [10]func() bool //not comparable
doit func() bool //not comparable
m map[string] string //not comparable
bytes []byte //not comparable
}
func main() {
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2:",v1 == v2)
}
Go確實(shí)提供了一些助手函數(shù)款筑,用于比較那些無(wú)法使用等號(hào)比較的變量智蝠。
最常用的方法是使用reflect包中的DeepEqual()
函數(shù)。
package main
import (
"fmt"
"reflect"
)
type data struct {
num int //ok
checks [10]func() bool //not comparable
doit func() bool //not comparable
m map[string] string //not comparable
bytes []byte //not comparable
}
func main() {
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2)) //prints: v1 == v2: true
m1 := map[string]string{"one": "a","two": "b"}
m2 := map[string]string{"two": "b", "one": "a"}
fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2)) //prints: m1 == m2: true
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2)) //prints: s1 == s2: true
}
除了很慢(這個(gè)可能會(huì)也可能不會(huì)影響你的應(yīng)用)奈梳,DeepEqual()
也有其他自身的技巧寻咒。
package main
import (
"fmt"
"reflect"
)
func main() {
var b1 []byte = nil
b2 := []byte{}
fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) //prints: b1 == b2: false
}
DeepEqual()
不會(huì)認(rèn)為空的slice
與“nil”的slice
相等。這個(gè)行為與你使用bytes.Equal()
函數(shù)的行為不同颈嚼。bytes.Equal()
認(rèn)為“nil”和空的slice是相等的毛秘。
package main
import (
"fmt"
"bytes"
)
func main() {
var b1 []byte = nil
b2 := []byte{}
fmt.Println("b1 == b2:",bytes.Equal(b1, b2)) //prints: b1 == b2: true
}
DeepEqual()
在比較slice時(shí)并不總是完美的。
package main
import (
"fmt"
"reflect"
"encoding/json"
)
func main() {
var str string = "one"
var in interface{} = "one"
fmt.Println("str == in:",str == in,reflect.DeepEqual(str, in))
//prints: str == in: true true
v1 := []string{"one","two"}
v2 := []interface{}{"one","two"}
fmt.Println("v1 == v2:",reflect.DeepEqual(v1, v2))
//prints: v1 == v2: false (not ok)
data := map[string]interface{}{
"code": 200,
"value": []string{"one","two"},
}
encoded, _ := json.Marshal(data)
var decoded map[string]interface{}
json.Unmarshal(encoded, &decoded)
fmt.Println("data == decoded:",reflect.DeepEqual(data, decoded))
//prints: data == decoded: false (not ok)
}
如果你的byte slice
(或者字符串)中包含文字?jǐn)?shù)據(jù)阻课,而當(dāng)你要不區(qū)分大小寫形式的值時(shí)(在使用==
叫挟,bytes.Equal()
,或者bytes.Compare()
)限煞,你可能會(huì)嘗試使用“bytes”和“string”包中的ToUpper()
或者ToLower()
函數(shù)抹恳。對(duì)于英語(yǔ)文本,這么做是沒(méi)問(wèn)題的署驻,但對(duì)于許多其他的語(yǔ)言來(lái)說(shuō)就不行了奋献。這時(shí)應(yīng)該使用strings.EqualFold()
和bytes.EqualFold()
健霹。
如果你的byte slice中包含需要驗(yàn)證用戶數(shù)據(jù)的隱私信息(比如,加密哈希瓶蚂、tokens等)糖埋,不要使用reflect.DeepEqual()
、bytes.Equal()
窃这,或者bytes.Compare()
瞳别,因?yàn)檫@些函數(shù)將會(huì)讓你的應(yīng)用易于被定時(shí)攻擊。為了避免泄露時(shí)間信息杭攻,使用'crypto/subtle'包中的函數(shù)(即祟敛,subtle.ConstantTimeCompare()
)。
從Panic中恢復(fù)
recover()
函數(shù)可以用于獲取/攔截panic
兆解。僅當(dāng)在一個(gè)defer
函數(shù)中被完成時(shí)馆铁,調(diào)用recover()
將會(huì)完成這個(gè)小技巧。
Incorrect:
package main
import "fmt"
func main() {
recover() //doesn't do anything
panic("not good")
recover() //won't be executed :)
fmt.Println("ok")
}
Works:
package main
import "fmt"
func main() {
defer func() {
fmt.Println("recovered:",recover())
}()
panic("not good")
}
recover()
的調(diào)用僅當(dāng)它在defer
函數(shù)中被直接調(diào)用時(shí)才有效锅睛。
Fails:
package main
import "fmt"
func doRecover() {
fmt.Println("recovered =>",recover()) //prints: recovered => <nil>
}
func main() {
defer func() {
doRecover() //panic is not recovered
}()
panic("not good")
}
在Slice, Array, and Map "range"語(yǔ)句中更新引用元素的值
在“range”語(yǔ)句中生成的數(shù)據(jù)的值是真實(shí)集合元素的拷貝叼架。它們不是原有元素的引用。
這意味著更新這些值將不會(huì)修改原來(lái)的數(shù)據(jù)衣撬。同時(shí)也意味著使用這些值的地址將不會(huì)得到原有數(shù)據(jù)的指針乖订。
package main
import "fmt"
func main() {
data := []int{1,2,3}
for _,v := range data {
v *= 10 //original item is not changed
}
fmt.Println("data:",data) //prints data: [1 2 3]
}
如果你需要更新原有集合中的數(shù)據(jù),使用索引操作符來(lái)獲得數(shù)據(jù)具练。
package main
import "fmt"
func main() {
data := []int{1,2,3}
for i,_ := range data {
data[i] *= 10
}
fmt.Println("data:",data) //prints data: [10 20 30]
}
如果你的集合保存的是指針乍构,那規(guī)則會(huì)稍有不同。
如果要更新原有記錄指向的數(shù)據(jù)扛点,你依然需要使用索引操作哥遮,但你可以使用for range語(yǔ)句中的第二個(gè)值來(lái)更新存儲(chǔ)在目標(biāo)位置的數(shù)據(jù)。
package main
import "fmt"
func main() {
data := []*struct{num int} { {1},{2},{3} }
for _,v := range data {
v.num *= 10
}
fmt.Println(data[0],data[1],data[2]) //prints &{10} &{20} &{30}
}
在Slice中"隱藏"數(shù)據(jù)
當(dāng)你重新劃分一個(gè)slice時(shí)陵究,新的slice將引用原有slice的數(shù)組眠饮。如果你忘了這個(gè)行為的話,在你的應(yīng)用分配大量臨時(shí)的slice用于創(chuàng)建新的slice來(lái)引用原有數(shù)據(jù)的一小部分時(shí)铜邮,會(huì)導(dǎo)致難以預(yù)期的內(nèi)存使用仪召。
package main
import "fmt"
func get() []byte {
raw := make([]byte,10000)
fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x>
return raw[:3]
}
func main() {
data := get()
fmt.Println(len(data),cap(data),&data[0]) //prints: 3 10000 <byte_addr_x>
}
為了避免這個(gè)陷阱,你需要從臨時(shí)的slice中拷貝數(shù)據(jù)(而不是重新劃分slice)松蒜。
package main
import "fmt"
func get() []byte {
raw := make([]byte,10000)
fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x>
res := make([]byte,3)
copy(res,raw[:3])
return res
}
func main() {
data := get()
fmt.Println(len(data),cap(data),&data[0]) //prints: 3 3 <byte_addr_y>
}
Slice的數(shù)據(jù)“毀壞”
比如說(shuō)你需要重新一個(gè)路徑(在slice中保存)扔茅。你通過(guò)修改第一個(gè)文件夾的名字,然后把名字合并來(lái)創(chuàng)建新的路勁秸苗,來(lái)重新劃分指向各個(gè)文件夾的路徑召娜。
package main
import (
"fmt"
"bytes"
)
func main() {
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path,'/')
dir1 := path[:sepIndex]
dir2 := path[sepIndex+1:]
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB
dir1 = append(dir1,"suffix"...)
path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB (not ok)
fmt.Println("new path =>",string(path))
}
結(jié)果與你想的不一樣。與"AAAAsuffix/BBBBBBBBB"相反惊楼,你將會(huì)得到"AAAAsuffix/uffixBBBB"玖瘸。這個(gè)情況的發(fā)生是因?yàn)閮蓚€(gè)文件夾的slice都潛在的引用了同一個(gè)原始的路徑slice秸讹。這意味著原始路徑也被修改了。根據(jù)你的應(yīng)用雅倒,這也許會(huì)是個(gè)問(wèn)題璃诀。
通過(guò)分配新的slice并拷貝需要的數(shù)據(jù),你可以修復(fù)這個(gè)問(wèn)題屯断。另一個(gè)選擇是使用完整的slice表達(dá)式。
package main
import (
"fmt"
"bytes"
)
func main() {
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path,'/')
dir1 := path[:sepIndex:sepIndex] //full slice expression
dir2 := path[sepIndex+1:]
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB
dir1 = append(dir1,"suffix"...)
path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB (ok now)
fmt.Println("new path =>",string(path))
}
完整的slice表達(dá)式中的額外參數(shù)可以控制新的slice的容量÷屡担現(xiàn)在在那個(gè)slice后添加元素將會(huì)觸發(fā)一個(gè)新的buffer分配殖演,而不是覆蓋第二個(gè)slice中的數(shù)據(jù)。
陳舊的(Stale)Slices
多個(gè)slice可以引用同一個(gè)數(shù)據(jù)年鸳。比如趴久,當(dāng)你從一個(gè)已有的slice創(chuàng)建一個(gè)新的slice時(shí),這就會(huì)發(fā)生搔确。如果你的應(yīng)用功能需要這種行為彼棍,那么你將需要關(guān)注下“走味的”slice。
在某些情況下膳算,在一個(gè)slice中添加新的數(shù)據(jù)座硕,在原有數(shù)組無(wú)法保持更多新的數(shù)據(jù)時(shí),將導(dǎo)致分配一個(gè)新的數(shù)組涕蜂。而現(xiàn)在其他的slice還指向老的數(shù)組(和老的數(shù)據(jù))华匾。
import "fmt"
func main() {
s1 := []int{1,2,3}
fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3]
s2 := s1[1:]
fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3]
for i := range s2 { s2[i] += 20 }
//still referencing the same array
fmt.Println(s1) //prints [1 22 23]
fmt.Println(s2) //prints [22 23]
s2 = append(s2,4)
for i := range s2 { s2[i] += 10 }
//s1 is now "stale"
fmt.Println(s1) //prints [1 22 23]
fmt.Println(s2) //prints [32 33 14]
}
類型聲明和方法
當(dāng)你通過(guò)把一個(gè)現(xiàn)有(非interface)的類型定義為一個(gè)新的類型時(shí),新的類型不會(huì)繼承現(xiàn)有類型的方法机隙。
Fails:
package main
import "sync"
type myMutex sync.Mutex
func main() {
var mtx myMutex
mtx.Lock() //error
mtx.Unlock() //error
}
Compile Errors:
/tmp/sandbox106401185/main.go:9: mtx.Lock undefined (type myMutex has no field or method Lock) /tmp/sandbox106401185/main.go:10: mtx.Unlock undefined (type myMutex has no field or method Unlock)
如果你確實(shí)需要原有類型的方法蜘拉,你可以定義一個(gè)新的struct類型,用匿名方式把原有類型嵌入其中有鹿。
Works:
package main
import "sync"
type myLocker struct {
sync.Mutex
}
func main() {
var lock myLocker
lock.Lock() //ok
lock.Unlock() //ok
}
interface類型的聲明也會(huì)保留它們的方法集合旭旭。
Works:
package main
import "sync"
type myLocker sync.Locker
func main() {
var lock myLocker = new(sync.Mutex)
lock.Lock() //ok
lock.Unlock() //ok
}
從"for switch"和"for select"代碼塊中跳出
沒(méi)有標(biāo)簽的“break”聲明只能從內(nèi)部的switch/select代碼塊中跳出來(lái)。如果無(wú)法使用“return”聲明的話葱跋,那就為外部循環(huán)定義一個(gè)標(biāo)簽是另一個(gè)好的選擇持寄。
package main
import "fmt"
func main() {
loop:
for {
switch {
case true:
fmt.Println("breaking out...")
break loop
}
}
fmt.Println("out!")
}
"goto"聲明也可以完成這個(gè)功能。娱俺。际看。
"for"聲明中的迭代變量和閉包
這在Go中是個(gè)很常見(jiàn)的技巧。for語(yǔ)句中的迭代變量在每次迭代時(shí)被重新使用矢否。這就意味著你在for循環(huán)中創(chuàng)建的閉包(即函數(shù)字面量)將會(huì)引用同一個(gè)變量(而在那些goroutine開始執(zhí)行時(shí)就會(huì)得到那個(gè)變量的值)仲闽。
Incorrect:
package main
import (
"fmt"
"time"
)
func main() {
data := []string{"one","two","three"}
for _,v := range data {
go func() {
fmt.Println(v)
}()
}
time.Sleep(3 * time.Second)
//goroutines print: three, three, three
}
最簡(jiǎn)單的解決方法(不需要修改goroutine)是,在for循環(huán)代碼塊內(nèi)把當(dāng)前迭代的變量值保存到一個(gè)局部變量中僵朗。
Works:
package main
import (
"fmt"
"time"
)
func main() {
data := []string{"one","two","three"}
for _,v := range data {
vcopy := v //
go func() {
fmt.Println(vcopy)
}()
}
time.Sleep(3 * time.Second)
//goroutines print: one, two, three
}
另一個(gè)解決方法是把當(dāng)前的迭代變量作為匿名goroutine的參數(shù)赖欣。
Works:
package main
import (
"fmt"
"time"
)
func main() {
data := []string{"one","two","three"}
for _,v := range data {
go func(in string) {
fmt.Println(in)
}(v)
}
time.Sleep(3 * time.Second)
//goroutines print: one, two, three
}
下面這個(gè)陷阱稍微復(fù)雜一些的版本屑彻。
Incorrect:
package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []field{ {"one"},{"two"},{"three"} }
for _,v := range data {
go v.print()
}
time.Sleep(3 * time.Second)
//goroutines print: three, three, three
}
Works:
package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []field{ {"one"},{"two"},{"three"} }
for _,v := range data {
v := v
go v.print()
}
time.Sleep(3 * time.Second)
//goroutines print: one, two, three
}
在運(yùn)行這段代碼時(shí)你認(rèn)為會(huì)看到什么結(jié)果?(原因是什么顶吮?)
package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []*field{ {"one"},{"two"},{"three"} }
for _,v := range data {
go v.print()
}
time.Sleep(3 * time.Second)
}
Defer函數(shù)調(diào)用參數(shù)的求值
被defer
的函數(shù)的參數(shù)會(huì)在defer
聲明時(shí)求值(而不是在函數(shù)實(shí)際執(zhí)行時(shí))社牲。
Arguments for a deferred function call are evaluated when the defer statement is evaluated (not when the function is actually executing).
package main
import "fmt"
func main() {
var i int = 1
defer fmt.Println("result =>",func() int { return i * 2 }())
i++
//prints: result => 2 (not ok if you expected 4)
}
被Defer的函數(shù)調(diào)用執(zhí)行
被defer的調(diào)用會(huì)在包含的函數(shù)的末尾執(zhí)行,而不是包含代碼塊的末尾悴了。對(duì)于Go新手而言搏恤,一個(gè)很常犯的錯(cuò)誤就是無(wú)法區(qū)分被defer的代碼執(zhí)行規(guī)則和變量作用規(guī)則。如果你有一個(gè)長(zhǎng)時(shí)運(yùn)行的函數(shù)湃交,而函數(shù)內(nèi)有一個(gè)for循環(huán)試圖在每次迭代時(shí)都defer資源清理調(diào)用熟空,那就會(huì)出現(xiàn)問(wèn)題。
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
if len(os.Args) != 2 {
os.Exit(-1)
}
start, err := os.Stat(os.Args[1])
if err != nil || !start.IsDir(){
os.Exit(-1)
}
var targets []string
filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if !fi.Mode().IsRegular() {
return nil
}
targets = append(targets,fpath)
return nil
})
for _,target := range targets {
f, err := os.Open(target)
if err != nil {
fmt.Println("bad target:",target,"error:",err) //prints error: too many open files
break
}
defer f.Close() //will not be closed at the end of this code block
//do something with the file...
}
}
解決這個(gè)問(wèn)題的一個(gè)方法是把代碼塊寫成一個(gè)函數(shù)搞莺。
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
if len(os.Args) != 2 {
os.Exit(-1)
}
start, err := os.Stat(os.Args[1])
if err != nil || !start.IsDir(){
os.Exit(-1)
}
var targets []string
filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if !fi.Mode().IsRegular() {
return nil
}
targets = append(targets,fpath)
return nil
})
for _,target := range targets {
func() {
f, err := os.Open(target)
if err != nil {
fmt.Println("bad target:",target,"error:",err)
return
}
defer f.Close() //ok
//do something with the file...
}()
}
}
另一個(gè)方法是去掉defer
語(yǔ)句 :-)
失敗的類型斷言
失敗的類型斷言返回?cái)嘌月暶髦惺褂玫哪繕?biāo)類型的“零值”息罗。這在與隱藏變量混合時(shí),會(huì)發(fā)生未知情況才沧。
Incorrect:
package main
import "fmt"
func main() {
var data interface{} = "great"
if data, ok := data.(int); ok {
fmt.Println("[is an int] value =>",data)
} else {
fmt.Println("[not an int] value =>",data)
//prints: [not an int] value => 0 (not "great")
}
}
Works:
package main
import "fmt"
func main() {
var data interface{} = "great"
if res, ok := data.(int); ok {
fmt.Println("[is an int] value =>",res)
} else {
fmt.Println("[not an int] value =>",data)
//prints: [not an int] value => great (as expected)
}
}
阻塞的Goroutine和資源泄露
Rob Pike在2012年的Google I/O大會(huì)上所做的“Go Concurrency Patterns”的演講上迈喉,說(shuō)道過(guò)幾種基礎(chǔ)的并發(fā)模式。從一組目標(biāo)中獲取第一個(gè)結(jié)果就是其中之一温圆。
func First(query string, replicas ...Search) Result {
c := make(chan Result)
searchReplica := func(i int) { c <- replicas[i](query) }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
這個(gè)函數(shù)在每次搜索重復(fù)時(shí)都會(huì)起一個(gè)goroutine挨摸。每個(gè)goroutine把它的搜索結(jié)果發(fā)送到結(jié)果的channel中。結(jié)果channel的第一個(gè)值被返回岁歉。
那其他goroutine的結(jié)果會(huì)怎樣呢油坝?還有那些goroutine自身呢?
在First()
函數(shù)中的結(jié)果channel是沒(méi)緩存的刨裆。這意味著只有第一個(gè)goroutine返回澈圈。其他的goroutine會(huì)困在嘗試發(fā)送結(jié)果的過(guò)程中。這意味著帆啃,如果你有不止一個(gè)的重復(fù)時(shí)瞬女,每個(gè)調(diào)用將會(huì)泄露資源。
為了避免泄露努潘,你需要確保所有的goroutine退出诽偷。一個(gè)不錯(cuò)的方法是使用一個(gè)有足夠保存所有緩存結(jié)果的channel。
func First(query string, replicas ...Search) Result {
c := make(chan Result,len(replicas))
searchReplica := func(i int) { c <- replicas[i](query) }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
另一個(gè)不錯(cuò)的解決方法是使用一個(gè)有default情況的select語(yǔ)句和一個(gè)保存一個(gè)緩存結(jié)果的channel疯坤。default情況保證了即使當(dāng)結(jié)果channel無(wú)法收到消息的情況下报慕,goroutine也不會(huì)堵塞。
func First(query string, replicas ...Search) Result {
c := make(chan Result,1)
searchReplica := func(i int) {
select {
case c <- replicas[i](query):
default:
}
}
for i := range replicas {
go searchReplica(i)
}
return <-c
}
你也可以使用特殊的取消channel來(lái)終止workers压怠。
func First(query string, replicas ...Search) Result {
c := make(chan Result)
done := make(chan struct{})
defer close(done)
searchReplica := func(i int) {
select {
case c <- replicas[i](query):
case <- done:
}
}
for i := range replicas {
go searchReplica(i)
}
return <-c
}
為何在演講中會(huì)包含這些bug眠冈?Rob Pike僅僅是不想把演示復(fù)雜化。這么作是合理的菌瘫,但對(duì)于Go新手而言蜗顽,可能會(huì)直接使用代碼布卡,而不去思考它可能有問(wèn)題。
高級(jí)
使用指針接收方法的值的實(shí)例
只要值是可取址的雇盖,那在這個(gè)值上調(diào)用指針接收方法是沒(méi)問(wèn)題的忿等。換句話說(shuō),在某些情況下崔挖,你不需要在有一個(gè)接收值的方法版本贸街。
然而并不是所有的變量是可取址的。Map的元素就不是狸相。通過(guò)interface引用的變量也不是薛匪。
package main
import "fmt"
type data struct {
name string
}
func (p *data) print() {
fmt.Println("name:",p.name)
}
type printer interface {
print()
}
func main() {
d1 := data{"one"}
d1.print() //ok
var in printer = data{"two"} //error
in.print()
m := map[string]data {"x":data{"three"}}
m["x"].print() //error
}
Compile Errors:
/tmp/sandbox017696142/main.go:21: cannot use data literal (type data) as type printer in assignment: data does not implement printer (print method has pointer receiver)
/tmp/sandbox017696142/main.go:25: cannot call pointer method on m["x"]
/tmp/sandbox017696142/main.go:25: cannot take the address of m["x"]
更新Map的值
如果你有一個(gè)struct值的map,你無(wú)法更新單個(gè)的struct值卷哩。
Fails:
package main
type data struct {
name string
}
func main() {
m := map[string]data {"x":{"one"}}
m["x"].name = "two" //error
}
Compile Error:
/tmp/sandbox380452744/main.go:9: cannot assign to m["x"].name
這個(gè)操作無(wú)效是因?yàn)閙ap元素是無(wú)法取址的蛋辈。
而讓Go新手更加困惑的是slice元素是可以取址的属拾。
package main
import "fmt"
type data struct {
name string
}
func main() {
s := []data one
s[0].name = "two" //ok
fmt.Println(s) //prints: [{two}]
}
注意在不久之前将谊,使用編譯器之一(gccgo)是可以更新map的元素值的,但這一行為很快就被修復(fù)了 :-)它也被認(rèn)為是Go 1.3的潛在特性渐白。在那時(shí)還不是要急需支持的尊浓,但依舊在todo list中。
第一個(gè)有效的方法是使用一個(gè)臨時(shí)變量纯衍。
package main
import "fmt"
type data struct {
name string
}
func main() {
m := map[string]data {"x":{"one"}}
r := m["x"]
r.name = "two"
m["x"] = r
fmt.Printf("%v",m) //prints: map[x:{two}]
}
另一個(gè)有效的方法是使用指針的map栋齿。
package main
import "fmt"
type data struct {
name string
}
func main() {
m := map[string]*data {"x":{"one"}}
m["x"].name = "two" //ok
fmt.Println(m["x"]) //prints: &{two}
}
順便說(shuō)下,當(dāng)你運(yùn)行下面的代碼時(shí)會(huì)發(fā)生什么襟诸?
package main
type data struct {
name string
}
func main() {
m := map[string]*data {"x":{"one"}}
m["z"].name = "what?" //???
}
"nil" Interfaces和"nil" Interfaces的值
這在Go中是第二最常見(jiàn)的技巧瓦堵,因?yàn)閕nterface雖然看起來(lái)像指針,但并不是指針歌亲。interface變量?jī)H在類型和值為“nil”時(shí)才為“nil”菇用。
interface的類型和值會(huì)根據(jù)用于創(chuàng)建對(duì)應(yīng)interface變量的類型和值的變化而變化。當(dāng)你檢查一個(gè)interface變量是否等于“nil”時(shí)陷揪,這就會(huì)導(dǎo)致未預(yù)期的行為惋鸥。
package main
import "fmt"
func main() {
var data *byte
var in interface{}
fmt.Println(data,data == nil) //prints: <nil> true
fmt.Println(in,in == nil) //prints: <nil> true
in = data
fmt.Println(in,in == nil) //prints: <nil> false
//'data' is 'nil', but 'in' is not 'nil'
}
當(dāng)你的函數(shù)返回interface時(shí),小心這個(gè)陷阱悍缠。
Incorrect:
package main
import "fmt"
func main() {
doit := func(arg int) interface{} {
var result *struct{} = nil
if(arg > 0) {
result = &struct{}{}
}
return result
}
if res := doit(-1); res != nil {
fmt.Println("good result:",res) //prints: good result: <nil>
//'res' is not 'nil', but its value is 'nil'
}
}
Works:
package main
import "fmt"
func main() {
doit := func(arg int) interface{} {
var result *struct{} = nil
if(arg > 0) {
result = &struct{}{}
} else {
ret
棧和堆變量
你并不總是知道變量是分配到棧還是堆上卦绣。在C++中,使用new創(chuàng)建的變量總是在堆上飞蚓。在Go中滤港,即使是使用new()
或者make()
函數(shù)來(lái)分配,變量的位置還是由編譯器決定趴拧。編譯器根據(jù)變量的大小和“泄露分析”的結(jié)果來(lái)決定其位置蜗搔。這也意味著在局部變量上返回引用是沒(méi)問(wèn)題的劲藐,而這在C或者C++這樣的語(yǔ)言中是不行的。
如果你想知道變量分配的位置樟凄,在“go build”或“go run”上傳入“-m“ gc標(biāo)志(即聘芜,go run -gcflags -m app.go)。
GOMAXPROCS, 并發(fā), 和并行
默認(rèn)情況下缝龄,Go僅使用一個(gè)執(zhí)行上下文/OS線程(在當(dāng)前的版本)汰现。這個(gè)數(shù)量可以通過(guò)設(shè)置GOMAXPROCS
來(lái)提高。
一個(gè)常見(jiàn)的誤解是叔壤,GOMAXPROCS
表示了CPU的數(shù)量瞎饲,Go將使用這個(gè)數(shù)量來(lái)運(yùn)行g(shù)oroutine。而runtime.GOMAXPROCS()
函數(shù)的文檔讓人更加的迷茫炼绘。GOMAXPROCS
變量描述(https://golang.org/pkg/runtime/)所討論OS線程的內(nèi)容比較好嗅战。
你可以設(shè)置GOMAXPROCS
的數(shù)量大于CPU的數(shù)量。GOMAXPROCS
的最大值是256俺亮。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 1
fmt.Println(runtime.NumCPU()) //prints: 1 (on play.golang.org)
runtime.GOMAXPROCS(20)
fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 20
runtime.GOMAXPROCS(300)
fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 256
}
讀寫操作的重排順序
Go可能會(huì)對(duì)某些操作進(jìn)行重新排序驮捍,但它能保證在一個(gè)goroutine內(nèi)的所有行為順序是不變的。然而脚曾,它并不保證多goroutine的執(zhí)行順序东且。
package main
import (
"runtime"
"time"
)
var _ = runtime.GOMAXPROCS(3)
var a, b int
func u1() {
a = 1
b = 2
}
func u2() {
a = 3
b = 4
}
func p() {
println(a)
println(b)
}
func main() {
go u1()
go u2()
go p()
time.Sleep(1 * time.Second)
}
如果你多運(yùn)行幾次上面的代碼,你可能會(huì)發(fā)現(xiàn)a和b變量有多個(gè)不同的組合:
1
2
3
4
0
2
0
0
1
4
a
和b
最有趣的組合式是"02"本讥。這表明b
在a
之前更新了珊泳。
如果你需要在多goroutine內(nèi)放置讀寫順序的變化,你將需要使用channel拷沸,或者使用"sync"包構(gòu)建合適的結(jié)構(gòu)體色查。
優(yōu)先調(diào)度
有可能會(huì)出現(xiàn)這種情況,一個(gè)無(wú)恥的goroutine阻止其他goroutine運(yùn)行撞芍。當(dāng)你有一個(gè)不讓調(diào)度器運(yùn)行的for循環(huán)時(shí)秧了,這就會(huì)發(fā)生。
package main
import "fmt"
func main() {
done := false
go func(){
done = true
}()
for !done {
}
fmt.Println("done!")
}
for循環(huán)并不需要是空的勤庐。只要它包含了不會(huì)觸發(fā)調(diào)度執(zhí)行的代碼示惊,就會(huì)發(fā)生這種問(wèn)題。
調(diào)度器會(huì)在GC愉镰、“go”聲明米罚、阻塞channel操作、阻塞系統(tǒng)調(diào)用和lock操作后運(yùn)行丈探。它也會(huì)在非內(nèi)聯(lián)函數(shù)調(diào)用后執(zhí)行录择。
package main
import "fmt"
func main() {
done := false
go func(){
done = true
}()
for !done {
fmt.Println("not done!") //not inlined
}
fmt.Println("done!")
}
要想知道你在for循環(huán)中調(diào)用的函數(shù)是否是內(nèi)聯(lián)的,你可以在“go build”或“go run”時(shí)傳入“-m” gc標(biāo)志(如, go build -gcflags -m
)隘竭。
另一個(gè)選擇是顯式的喚起調(diào)度器塘秦。你可以使用“runtime”包中的Goshed()
函數(shù)。
package main
import (
"fmt"
"runtime"
)
func main() {
done := false
go func(){
done = true
}()
for !done {
runtime.Gosched()
}
fmt.Println("done!")
}