這里列舉的Go語(yǔ)言常見(jiàn)坑都是符合Golang語(yǔ)法的,可以正常的編譯嘁捷,但是可能是運(yùn)行結(jié)果錯(cuò)誤造成,或者是有資源泄漏的風(fēng)險(xiǎn)。
可變參數(shù)是空接口類型
當(dāng)參數(shù)的可變參數(shù)是空接口類型時(shí)雄嚣,傳人空接口的切片時(shí)需要注意參數(shù)展開(kāi)的問(wèn)題晒屎。
func main() {
var a = []interface{}{1, 2, 3}
fmt.Println(a)
fmt.Println(a...)
}
// 不管是否展開(kāi),編譯器都無(wú)法發(fā)現(xiàn)錯(cuò)誤缓升,但是輸出是不同的:
// [1 2 3]
// 1 2 3
數(shù)組是值傳遞
在函數(shù)調(diào)用參數(shù)中鼓鲁,數(shù)組是值傳遞,無(wú)法通過(guò)修改數(shù)組類型的參數(shù)返回結(jié)果港谊,必要時(shí)需要使用切片骇吭。
func main() {
x := [3]int{1, 2, 3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr)
}(x)
fmt.Println(x)
}
// [7 2 3]
// [1 2 3]
map遍歷順序是不固定的
map是一種hash表實(shí)現(xiàn),每次遍歷的順序都可能不一樣歧寺。Golang會(huì)提前取一個(gè)隨機(jī)數(shù)燥狰,把桶的遍歷順序隨機(jī)化。
func main() {
m := map[string]string{
"1": "1",
"2": "2",
"3": "3",
}
for k, v := range m {
println(k, v)
}
}
recover必須在defer函數(shù)中運(yùn)行
recover捕獲的是祖父級(jí)調(diào)用時(shí)的異常斜筐,直接調(diào)用無(wú)效龙致;
直接defer調(diào)用無(wú)效
func main() {
defer recover()
panic(1)
}
defer調(diào)用時(shí)多層嵌套依然無(wú)效
func main() {
defer func() {
func() { recover() }()
}()
panic(1)
}
必須在defer函數(shù)中直接調(diào)用才有效
func main() {
defer func() {
recover()
}()
panic(1)
}
main函數(shù)提前退出
導(dǎo)致后臺(tái)Goroutine無(wú)法保證完成任務(wù)。
休眠:
time.Sleep(time.Second)
插入調(diào)度語(yǔ)句:
runtime.Gosched() //用于讓出當(dāng)前CPU時(shí)間片
利用for:
for {}
利用for+調(diào)度語(yǔ)句:
for {
runtime.Gosched()
}
利用阻塞:
select {}
閉包錯(cuò)誤引用同一個(gè)變量
func main() {
for i := 0; i < 5; i++ {
defer func() {
println(i)
}()
}
}
/*
5
5
5
5
5
*/
改進(jìn)的方法是在每輪迭代中生成一個(gè)局部變量
func main() {
for i := 0; i < 5; i++ {
i := i
defer func() {
println(i)
}()
}
}
/*
4
3
2
1
0
*/
或者是通過(guò)函數(shù)參數(shù)傳入:
func main() {
for i := 0; i < 5; i++ {
defer func(i int) {
println(i)
}(i)
}
}
/*
4
3
2
1
0
*/
在循環(huán)內(nèi)部執(zhí)行defer語(yǔ)句
defer在函數(shù)退出時(shí)才能執(zhí)行顷链,在for執(zhí)行defer會(huì)導(dǎo)致資源延遲釋放:
func main() {
for i := 0; i < 5; i++ {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
}
解決的方法可以在for中構(gòu)造一個(gè)局部函數(shù)目代,在局部函數(shù)內(nèi)部執(zhí)行defer:
func main() {
for i := 0; i < 5; i++ {
func() {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}()
}
}
切片會(huì)導(dǎo)致整個(gè)底層數(shù)組被鎖定
切片會(huì)導(dǎo)致整個(gè)底層數(shù)組被鎖定,底層數(shù)組無(wú)法釋放內(nèi)存。如果底層數(shù)組較大會(huì)對(duì)內(nèi)存產(chǎn)生很大的壓力榛了。
headerMap[name] = data[:1]
解決的方法是將結(jié)果克隆一份在讶,這樣可以釋放底層的數(shù)組:
eaderMap[name] = append([]byte{}, data[:1]...)
空指針和空接口不等價(jià)
返回了一個(gè)錯(cuò)誤指針,但是并不是空的error接口:
內(nèi)存地址會(huì)變化
Go語(yǔ)言中對(duì)象的地址可能發(fā)生變化忽冻,因此指針不能從其它非指針類型的值生成:
func main() {
var x int = 42
var p uintptr = uintptr(unsafe.Pointer(&x))
runtime.GC()
var px *int = (*int)(unsafe.Pointer(p))
println(*px)
}
當(dāng)內(nèi)存發(fā)送變化的時(shí)候真朗,相關(guān)的指針會(huì)同步更新,但是非指針類型的uintptr不會(huì)做同步更新僧诚。
同理CGO中也不能保存Go對(duì)象地址。
Goroutine泄露
Go語(yǔ)言是帶內(nèi)存自動(dòng)回收的特性蝗碎,因此內(nèi)存一般不會(huì)泄漏湖笨。但是Goroutine確存在泄漏的情況,同時(shí)泄漏的Goroutine引用的內(nèi)存同樣無(wú)法被回收蹦骑。
func main() {
ch := func() <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
} ()
return ch
}()
for v := range ch {
fmt.Println(v)
if v == 5 {
break
}
}
}
上面的程序中后臺(tái)Goroutine向管道輸入自然數(shù)序列慈省,main函數(shù)中輸出序列。但是當(dāng)break跳出for循環(huán)的時(shí)候眠菇,后臺(tái)Goroutine就處于無(wú)法被回收的狀態(tài)了边败。
我們可以通過(guò)context包來(lái)避免這個(gè)問(wèn)題:
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := func(ctx context.Context) <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
select {
case <- ctx.Done():
return
case ch <- i:
}
}
} ()
return ch
}(ctx)
for v := range ch {
fmt.Println(v)
if v == 5 {
cancel()
break
}
}
}
當(dāng)main函數(shù)在break跳出循環(huán)時(shí),通過(guò)調(diào)用cancel()
來(lái)通知后臺(tái)Goroutine退出捎废,這樣就避免了Goroutine的泄漏笑窜。