可變參數是空接口類型
當參數的可變參數是空接口類型時,傳入空接口的切片時需要注意參數展開的問題。
func?main()?{
????var?a?=?[]interface{}{1,?2,?3}
????fmt.Println(a)//傳入切片類型
????fmt.Println(a...)//傳入切片的值
}
不管是否展開,編譯器都無法發現錯誤,但是輸出是不同的:
[1?2?3] 1?2?3
數組是值傳遞
在函數調用參數中,數組是值傳遞,無法通過修改數組類型的參數返回結果。必要時需要使用切片。
func?main()?{
????x?:=?[3]int{1,?2,?3}
????func(arr?[3]int)?{
????????arr[0]?=?7
????????fmt.Println(arr)
????}(x)
????fmt.Println(x)
}map遍歷是順序不固定
map是一種hash表實現,每次遍歷的順序都可能不一樣。
func?main()?{
????m?:=?map[string]string{
????????"1":?"1",
????????"2":?"2",
????????"3":?"3",
????}
????for?k,?v?:=?range?m?{
????????println(k,?v)
????}
}
返回值被屏蔽
在局部作用域中,命名的返回值內同名的局部變量屏蔽:
func?Foo()?(err?error)?{
????if?err?:=?Bar();?err?!=?nil?{//內部變量err與返回值err沖突了
????????return
????}
????return
}
recover必須在defer函數中運行
recover捕獲的是祖父級調用時的異常,直接調用時無效:
func?main()?{
????recover()
????panic(1)
}
直接defer調用也是無效:
func?main()?{
????defer?recover()
????panic(1)
}
defer調用時多層嵌套依然無效:
func?main()?{
????defer?func()?{
????????func()?{?recover()?}()
????}()
????panic(1)
}
必須在defer函數中直接調用才有效:
func?main()?{
????defer?func()?{
????????recover()
????}()
????panic(1)
}main函數提前退出
后臺Goroutine無法保證完成任務。
func?main()?{
????go?println("hello")//main不會等待新協程,新協程可能被中斷
}獨占CPU導致其它Goroutine餓死
Goroutine是協作式搶占調度,Goroutine本身不會主動放棄CPU:
func?main()?{
????runtime.GOMAXPROCS(1)
????go?func()?{
????????for?i?:=?0;?i?10;?i++?{
????????????fmt.Println(i)
????????}
????}()
????for?{}?//?占用CPU
}
解決的方法是在for循環加入runtime.Gosched()調度函數:
func?main()?{
????runtime.GOMAXPROCS(1)
????go?func()?{
????????for?i?:=?0;?i?10;?i++?{
????????????fmt.Println(i)
????????}
????}()
????for?{
????????runtime.Gosched()
????}
}
或者是通過阻塞的方式避免CPU占用:
func?main()?{
????runtime.GOMAXPROCS(1)
????go?func()?{
????????for?i?:=?0;?i?10;?i++?{
????????????fmt.Println(i)
????????}
????????os.Exit(0)
????}()
????select{}
}不同Goroutine之間不滿足順序一致性內存模型
因為在不同的Goroutine,main函數中無法保證能打印出hello, world:
var?msg?string
var?done?bool
func?setup()?{
????msg?=?"hello,?world"
????done?=?true//在main協程中看來,done語句不一定在msg之后完成
}
func?main()?{
????go?setup()
????for?!done?{
????}
????println(msg)
}解決的辦法是用chan顯式同步:
var?msg?string
var?done?=?make(chan?bool)
func?setup()?{
????msg?=?"hello,?world"
????done?<-?true
}
func?main()?{
????go?setup()
????<-done
????println(msg)
}
msg的寫入是在channel發送之前,所以能保證打印hello, world
defer錯誤引用同一個變量
func?main()?{
????for?i?:=?0;?i?5;?i++?{
????????defer?func()?{
????????????println(i)//這里打印一直是5,不是4,3,2,1,0
????????}()
????}
}
改進的方法是通過函數參數傳入:
func?main()?{
????for?i?:=?0;?i?5;?i++?{
????????defer?func(i?int)?{
????????????println(i)
????????}(i)
????}
}
切片會導致整個底層數組被鎖定
切片會導致整個底層數組被鎖定,底層數組無法釋放內存。如果底層數組較大會對內存產生很大的壓力。
func?main()?{
????headerMap?:=?make(map[string][]byte)
????for?i?:=?0;?i?5;?i++?{
????????name?:=?"/path/to/file"
????????data,?err?:=?ioutil.ReadFile(name)
????????if?err?!=?nil?{
????????????log.Fatal(err)
????????}
????????//創建了data的切片,導致data不會被釋放,文件全部內容一直在內存中
????????headerMap[name]?=?data[:1]
????}
????//?do?some?thing
}
解決的方法是將結果克隆一份,這樣可以釋放底層的數組:
func?main()?{
????headerMap?:=?make(map[string][]byte)
????for?i?:=?0;?i?5;?i++?{
????????name?:=?"/path/to/file"
????????data,?err?:=?ioutil.ReadFile(name)
????????if?err?!=?nil?{
????????????log.Fatal(err)
????????}
????????headerMap[name]?=?append([]byte{},?data[:1]...)
????}
????//?do?some?thing
}空指針和空接口不等價
比如返回了一個錯誤指針,但是并不是空的error接口:
func?returnsError()?error?{
????var?p?*MyError?=?nil
????if?bad()?{
????????p?=?ErrBad
????}
????return?p?//?Will?always?return?a?non-nil?error.
}
內存地址會變化
Go語言中對象的地址可能發生變化,因此指針不能從其它非指針類型的值生成:
func?main()?{
????var?x?int?=?42
????var?p?uintptr?=?uintptr(unsafe.Pointer(&x))
????runtime.GC()//運行垃圾回收,內存地址會發送變化
????var?px?*int?=?(*int)(unsafe.Pointer(p))
????println(*px)
}當內存發送變化的時候,相關的指針會同步更新,但是非指針類型的uintptr不會做同步更新。同理CGO中也不能保存Go對象地址。
Goroutine泄露
Go語言是帶內存自動回收的特性,因此內存一般不會泄漏。但是Goroutine確存在泄漏的情況,同時泄漏的Goroutine引用的內存同樣無法被回收。
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//break之后,主協程不再讀取chan,新協程會一直阻塞
????????}
????}
}
上面的程序中后臺Goroutine向管道輸入自然數序列,main函數中輸出序列。但是當break跳出for循環的時候,后臺Goroutine就處于無法被回收的狀態了。我們可以通過context包來避免這個問題:
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
????????}
????}
}當main函數在break跳出循環時,通過調用cancel()來通知后臺Goroutine退出,這樣就避免了Goroutine的泄漏。
編輯:黃飛
?
電子發燒友App











評論