這個是比較常見的問題了,我自己也整理一下:
轉自:https://studygolang.com/articles/31108#reply2
參考:go語言中文文檔:www.topgoer.com
func main() { l := []int{1,2,3} fmt.Printf("%p \n", &l) for _, v := range l { fmt.Printf("%p : %d \n", &v,v) }}
輸出結果
0xc000092080 0xc00018a008 : 1 0xc00018a008 : 2 0xc00018a008 : 3
這邊基本可以看出來了,v是一個臨時分配出來的的內存,賦值為當前遍歷的值。因此就可能會導致兩個問題
func main() { l := []int{1, 2, 3} for _, v := range l { v+=1 } fmt.Println(l)}//[1 2 3]
func main() { m := make(map[string]*student) stus := []student{ {Name: "a"}, {Name: "b"}, {Name: "c"}, } for _, stu := range stus { m[stu.Name] = &stu } fmt.Println(m)}//map[a:0xc000012060 b:0xc000012060 c:0xc000012060]
如果怕用錯的話建議使用index,不要用value:
for i, _ := range list { list[i]//TODO}
先來看一下兩組代碼和答案:
未使用閉包
func main() { for i := 0; i < 5; i++ { defer fmt.Printf("%d %p ",i,&i) }}//4 0xc00009a008 3 0xc00009a008 2 0xc00009a008 1 0xc00009a008 0 0xc00009a008
使用閉包
func main() { for i := 0; i < 5; i++ { defer func() { fmt.Printf("%d %p ", i, &i) }() }}//5 0xc000096018 5 0xc000096018 5 0xc000096018 5 0xc000096018 5 0xc000096018
defer 是一個延時調用關鍵字,會在當前函數執行結束前才被執行,後面的函數先會被編譯,到了快結束前才會被輸出,而不是結束前再進行編譯。下面寫了一些代碼便於理解:
func main() { fmt.Println(time.Now().Second()) defer fmt.Println(time.Now().Second()) time.Sleep(time.Second)}//19//19func main() { fmt.Println(time.Now().Second()) defer func() { fmt.Println(time.Now().Second()) }() time.Sleep(time.Second)}//22//23
從上面代碼可以看出,defer是及時編譯的,因此在沒有閉包的情況下,時間是相同的,但是在加了閉包之後,遇到defer之後會對匿名函數進行編譯(不會進行函數內的操作),然後打入一個棧裡,到了最後才會執行函數內的操作,所以輸出不同。根據這個代碼再看一下上面的問題。第一個沒有閉包會直接對i進行取值放入棧裡面,最後輸出,因此可以得到想要的結果。但是當有了閉包之後,函數體裡的方法不會立即執行,這個i所表現的只是一個內存地址,在最後輸出時都指向了同一個地址,因此它的值是相同的。
了解原因之後,解決方法也就很簡單,既然原因是因為傳入參數的地址相同了,那使它不同就行了:
func main() { for i := 0; i < 5; i++ { //j:=i defer func(j int) { fmt.Printf("%d %p ", j, &j) }(i) }}//4 0xc000018330 3 0xc000018340 2 0xc000018350 1 0xc000018360 0 0xc000018370
這兩種寫法一樣,都是將當前的值賦值給一個新的對象(相當於指向了新的地址),不過給閉包函數加參數會顯得更加優雅一點。
這個問題在個人開發時幾乎不會考慮,當服務數據量很大時才需要注意一下,上一遍文章也專門寫了一下關於go裡面的map的相關內容,具體問題是由於map的刪除並不是真正的釋放內存空間,比如一個map裡面有1w個k-v,然後其中5k個不需要被刪除了,接著往裡面繼續添加1k個鍵值對,此時map所佔的內存大小很有可能仍為11k個鍵值對的大小,這將會導致所佔用的內存會越來越大,造成內存溢出。方法就是將原本map中有用的值重新加入到新的map中:
oldMap := make(map[int]int, 10000)newMap := make(map[int]int, len(oldMap))for k, v := range oldMap { newMap[k] = v}oldMap = newMap
方法是有了,但是到底該怎麼用呢?下面說一下我個人的看法:
協程洩漏是我同事開發時遇到的一個問題,這邊我也記錄一下。
什麼是協程洩漏,大體的意思是主程序已經跑完了,但是主程序中開的go協程沒有結束。如何知道協程是否發生了洩漏,最簡單的方法是runtime.NumGoroutine()得到結果是否與你的期望值一樣,如果大了就是發生了洩漏。
哪些問題會導致協程洩漏?
1、死循環
func main() { defer func() { time.Sleep(time.Second) fmt.Println("the number of goroutines: ", runtime.NumGoroutine()) }() go func() { select { } }()}//the number of goroutines: 2
2、鎖(chan的就是鎖+隊列的實現)
func queryAll(n int) int { ch := make(chan int) for i := 0; i < n; i++ { go func(i int) { time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) ch <- i }(i) } s := <-ch return s}func main() { queryAll(3) time.Sleep(time.Second) //查看一段時間後的協程數 fmt.Printf("the number of goroutines: %d", runtime.NumGoroutine())}//the number of goroutines: 3
死循環好理解,conrountinue一直在運行,沒有退出。
對於通道舉例說明:海陸空三路一起送一份郵件,只需要第一個送到的,main主協程為收件人,收件人開著門在門口等著收郵件,在收到第一個人的郵件時,門沒關就直接進屋研究去了(主協程結束),後面兩位過一會也到了,但是發現門沒關,認為家裡有人就一直在等著(協程堵塞,資源洩漏)。那麼這時候該怎麼辦?如何close了這個門,那後面兩個人到了發現門是關著的,這麼緊急的郵件居然關門了(並不知道有人已經送到了)就會認為可能出問題了,panic。正確的解決方案可以有下面幾個:
func queryAll(n int) int { ch := make(chan int, n) for i := 0; i < n; i++ { go func(i int) { time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) ch <- i }(i) } s := <-ch return s}func main() { queryAll(3) time.Sleep(time.Second) fmt.Printf("the number of goroutines: %d", runtime.NumGoroutine())}//the number of goroutines: 1
func queryAll(n int) int { ch := make(chan int) totla:=0 for i := 0; i < n; i++ { go func(i int) { time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) ch <- i }(i) } s := <-ch for range ch{ totla++ if totla==n-1{ close(ch) } } return s}func main() { queryAll(3) time.Sleep(time.Second) fmt.Printf("the number of goroutines: %d", runtime.NumGoroutine())}//the number of goroutines: 1
這個算是比較簡單的錯誤了,不關閉的話會發生內存洩漏,具體原因沒有了解,個人理解可以將response.body認為一個網絡型的os file,和你讀取本地文件效果一樣,數據被寫到緩存去了,不關閉的話將會佔用資源。
// An error is returned if there were too many redirects or if there// was an HTTP protocol error. A non-2xx response doesn't cause an// error. Any returned error will be of type *url.Error. The url.Error// value's Timeout method will report true if request timed out or was// canceled.//// When err is nil, resp always contains a non-nil resp.Body.// Caller should close resp.Body when done reading from it.//// Get is a wrapper around DefaultClient.Get.//// To make a request with custom headers, use NewRequest and// DefaultClient.Do.func Get(url string) (resp *Response, err error) { return DefaultClient.Get(url)}
Caller should close resp.Body when done reading from it. 這一句話 go/src/net/http/client.go 裡多次提到過了提過,注意一下就行