可變參數(shù)是空接口類(lèi)型
當(dāng)參數(shù)的可變參數(shù)是空接口類(lèi)型時(shí),傳入空接口的切片時(shí)需要注意參數(shù)展開(kāi)的問(wèn)題。
func?main()?{ ????var?a?=?[]interface{}{1,?2,?3} ????fmt.Println(a)//傳入切片類(lèi)型 ????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ù)組類(lèi)型的參數(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) }map遍歷是順序不固定
map是一種hash表實(shí)現(xiàn),每次遍歷的順序都可能不一樣。
func?main()?{
????m?:=?map[string]string{ ????????"1":?"1", ????????"2":?"2", ????????"3":?"3", ????} ????for?k,?v?:=?range?m?{ ????????println(k,?v) ????} }
返回值被屏蔽
在局部作用域中,命名的返回值內(nèi)同名的局部變量屏蔽:
func?Foo()?(err?error)?{
????if?err?:=?Bar();?err?!=?nil?{//內(nèi)部變量err與返回值err沖突了 ????????return ????} ????return }
recover必須在defer函數(shù)中運(yùn)行
recover捕獲的是祖父級(jí)調(diào)用時(shí)的異常,直接調(diào)用時(shí)無(wú)效:
func?main()?{ ????recover() ????panic(1) }
直接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ù)提前退出
后臺(tái)Goroutine無(wú)法保證完成任務(wù)。
func?main()?{
????go?println("hello")//main不會(huì)等待新協(xié)程,新協(xié)程可能被中斷
}獨(dú)占CPU導(dǎo)致其它Goroutine餓死
Goroutine是協(xié)作式搶占調(diào)度,Goroutine本身不會(huì)主動(dòng)放棄CPU:
func?main()?{ ????runtime.GOMAXPROCS(1) ????go?func()?{ ????????for?i?:=?0;?i?10;?i++?{ ????????????fmt.Println(i) ????????} ????}() ????for?{}?//?占用CPU }
解決的方法是在for循環(huán)加入runtime.Gosched()調(diào)度函數(shù):
func?main()?{ ????runtime.GOMAXPROCS(1) ????go?func()?{ ????????for?i?:=?0;?i?10;?i++?{ ????????????fmt.Println(i) ????????} ????}() ????for?{ ????????runtime.Gosched() ????} }
或者是通過(guò)阻塞的方式避免CPU占用:
func?main()?{
????runtime.GOMAXPROCS(1)
????go?func()?{
????????for?i?:=?0;?i?10;?i++?{
????????????fmt.Println(i)
????????}
????????os.Exit(0)
????}()
????select{}
}不同Goroutine之間不滿(mǎn)足順序一致性?xún)?nèi)存模型
因?yàn)樵诓煌腉oroutine,main函數(shù)中無(wú)法保證能打印出hello, world:
var?msg?string
var?done?bool
func?setup()?{
????msg?=?"hello,?world"
????done?=?true//在main協(xié)程中看來(lái),done語(yǔ)句不一定在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的寫(xiě)入是在channel發(fā)送之前,所以能保證打印hello, world
defer錯(cuò)誤引用同一個(gè)變量
func?main()?{ ????for?i?:=?0;?i?5;?i++?{ ????????defer?func()?{ ????????????println(i)//這里打印一直是5,不是4,3,2,1,0 ????????}() ????} }
改進(jìn)的方法是通過(guò)函數(shù)參數(shù)傳入:
func?main()?{ ????for?i?:=?0;?i?5;?i++?{ ????????defer?func(i?int)?{ ????????????println(i) ????????}(i) ????} }
切片會(huì)導(dǎo)致整個(gè)底層數(shù)組被鎖定
切片會(huì)導(dǎo)致整個(gè)底層數(shù)組被鎖定,底層數(shù)組無(wú)法釋放內(nèi)存。如果底層數(shù)組較大會(huì)對(duì)內(nèi)存產(chǎn)生很大的壓力。
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) ????????} ????????//創(chuàng)建了data的切片,導(dǎo)致data不會(huì)被釋放,文件全部?jī)?nèi)容一直在內(nèi)存中 ????????headerMap[name]?=?data[:1] ????} ????//?do?some?thing }
解決的方法是將結(jié)果克隆一份,這樣可以釋放底層的數(shù)組:
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
}空指針和空接口不等價(jià)
比如返回了一個(gè)錯(cuò)誤指針,但是并不是空的error接口:
func?returnsError()?error?{ ????var?p?*MyError?=?nil ????if?bad()?{ ????????p?=?ErrBad ????} ????return?p?//?Will?always?return?a?non-nil?error. }
內(nèi)存地址會(huì)變化
Go語(yǔ)言中對(duì)象的地址可能發(fā)生變化,因此指針不能從其它非指針類(lèi)型的值生成:
func?main()?{
????var?x?int?=?42
????var?p?uintptr?=?uintptr(unsafe.Pointer(&x))
????runtime.GC()//運(yùn)行垃圾回收,內(nèi)存地址會(huì)發(fā)送變化
????var?px?*int?=?(*int)(unsafe.Pointer(p))
????println(*px)
}當(dāng)內(nèi)存發(fā)送變化的時(shí)候,相關(guān)的指針會(huì)同步更新,但是非指針類(lèi)型的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//break之后,主協(xié)程不再讀取chan,新協(xié)程會(huì)一直阻塞 ????????} ????} }
上面的程序中后臺(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的泄漏。
編輯:黃飛
?
評(píng)論
查看更多