golang range遍歷問題筆記

例1:

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
}
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
}

這裏應該很好理解,閉包綁定了v,v的值會因爲遍歷不停的覆蓋,最終變爲最後一個元素。可以參考。所以需要在for中創建一個臨時變量來解決。

例2:

package main

import (
	"fmt"
	"runtime"
	"time"
)

type field struct {
	name string
}

func (p *field) print() {
	fmt.Println(p.name)
}

func (p field) print1() {
	fmt.Println(p.name)
}

func myprint(p *field) {
	fmt.Println(p.name)
}

func myprint2(p field) {
	fmt.Println(p.name)
}

func main() {
	runtime.GOMAXPROCS(1)

	data1 := []*field{{"one"}, {"two"}, {"three"}}

	for _, v := range data1 {
		go v.print()
		//go myprint(v)
		//go v.print1()
	}
	time.Sleep(time.Second)

	data2 := []field{{"four"}, {"five"}, {"six"}}
	for _, v := range data2 {
		go v.print()
		//go myprint(&v)
	}

	time.Sleep(time.Second)
    
    // print: one two three six six six
}

爲什麼值slice就輸出一樣的,但指針slice就沒有問題呢?

首先看下羣友“橘子汽水”的解答

循環裏的v一直在被修改,但v的地址沒變。第一個循環,v其實是指針類型,這樣v的值可以傳進print函數。第二個循環,v是struct,但是print函數的接收器要求是指針類型,編譯器會把代碼改寫成&v.print。但是v這個變量在循環內地址是固定的,因此3個print接收到的是同一個地址,這個地址的值在grouting執行前被改成了six

slice遍歷的時候,因爲print是指針接收者方法,所以編譯器會把v&v,因爲v的地址是不會變的,所以其他的goroutine通過地址讀取出來都是相同的值。

其實golang中接收者方法是一個語法糖,我們可以通過這個來幫助理解。

func (p field) print1() {
	fmt.Println(p.name)
}
p.print1() // 等同於print1(p)

所以例2中,可以用myprint來便於理解,值slice遍歷時,因爲第一個參數是指針類型,所以需要取指針myprint(&v),但是v的地址始終是不變的,所以最終取出來的值都變成一樣的了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章