注意: 此文章只是我的個人筆記,如有謬誤,錯誤, 請一定指出。
for range 問題
http://stackoverflow.com/questions/30577212/go-for-range-slice-and-goroutine-method-invocation-the-logic-behind
package main import ( "fmt" "time" ) type field struct { name string } func (p *field) print() { fmt.Printf("print: p: %p, v: %s\n", p, p.name) } func main() { data := []field{ {"one"},{"two"},{"three"} } for _,v := range data { // 注意:for語句中的迭代變數(如: v)在每次迭代時被重新使用, 一直複用 go v.print() // 注意: 此處可理解為: go (&v).print(), 也就是用v的指標去調用, 而且v會在每次迭代時複用,所以每一個調用的receiver都是共同指向v的指標, 而且v在最後一次迭代後, 被 賦值為:"three", 所以 才有了列印出3個"three"的結果. } time.Sleep(3 * time.Second) //goroutines print: three, three, three }
package main import ( "fmt" "time" ) type field struct { name string } func (p *field) print() { fmt.Println(p.name) } func main() { data := []*field{ {"one"},{"two"},{"three"} } for _,v := range data { go v.print() //v本身就是指標, 指向one, two, three; 迭代時會改變指向, 直接調用沒有複製,直接調用,故每次調用時v都是分別指向one, two , three的地址值 , 當然會列印出正確的結果 ; 與上例不同, 上例中,因為v的複用,每一次調用的pointer receiver都共同指向v, 但v的值在迴圈後最終賦值為:three, 所以出現錯誤結果。 } time.Sleep(3 * time.Second) } //goroutines print: one, two, three
注意:四個要素解釋以上兩例子:
前提條件:方法定義時為Pointer receiver.
func (
p *field) print() { fmt.Printf("print: p: %p, v: %s\n", p, p.name) }
(1) 滿足前提條件下, Pointer Receiver不複製,所以當以值方式調用 時, 直接 &value取地址作為pointer receiver.
(2)滿足前提條件下,若for _,v := range data中的 v本身就是指標, 則直接調用. (3)for range會在每次迭代中複用v (4)go語言本身是值語義的, 也就是說傳參,調用, 迭代都會複製, 只不過像:指標, 參考型別只是複製了其本身, 而非其指向的data, 這樣複製代價很低很低;不過數組為實值型別的,會整體複製喲。 後而參考資料連結中有詳細的分析。
//stackoverflow中的解釋: 這在Go中是個很常見的技巧。for語句中的迭代變數在每次迭代時被重新使用。這就意味著你在for迴圈中建立的閉包(即函數字面量)將會引用同一個變數(而在那些goroutine開始執行時就會得到那個變數的值)。
In the first loop, v is the value of a field item. Because v is addressable, it is automatically referenced as the pointer receiver for the print() method. So v.print() is using the address of v itself, and the contents of that address is overwritten each iteration of the loop. When you change the declaration to use a *field, v is now a pointer to a field value. When you call v.print() in this case, you are operating on the value that v points to, which is stored in data, and the overwriting of v has no effect.
----------------------------------------------------我的實驗代碼--------------------------------- package main
import "fmt" import "time"
type A int
func (a A) ValueReceiver(){
fmt.Printf("ValueReceiver, p: %p, v: %d\n", &a, a) } func (a A) ValueReceiverIngo(){
fmt.Printf("ValueReceiverIngo, p: %p, v:%d\n", &a, a) } func (a A) ValueReceiverInDefer(){
fmt.Printf("ValueReceiverInDefer, p: %p, v:%d\n", &a, a) }
func (a A) ValueRececiverInforRange(){
fmt.Printf("ValueRececiverInforRange, p: %p, v: %d\n", &a, a) } func main() { var a A = 1 fmt.Printf("main, p: %p, v: %d\n", &a, a) a.ValueReceiver() p := &a p.ValueReceiver() //------------call in goroutine-------- go a.ValueReceiverIngo() go p.ValueReceiverIngo() time.Sleep(3* time.Second) //---------call in defer---------- defer a.ValueReceiverInDefer() defer p.ValueReceiverInDefer() //---call in for range array, value receiver--- as := [5]A{1,2,3,4,5} fmt.Printf("as[0]: %p, %d\n", &as[0], as[0]) for _, a := range as { fmt.Printf("as in for: %p, %d\n", &as[0], as[0]) fmt.Printf("a in for: %p, v: %d\n", &a, a) a.ValueRececiverInforRange() pf := &a pf.ValueRececiverInforRange() } }
//結果 main, p: 0x10434114, v: 1
ValueReceiver, p: 0x1043411c, v: 1
ValueReceiver, p: 0x10434134, v: 1
ValueReceiverIngo, p: 0x1043413c, v:1
ValueReceiverIngo, p: 0x10434144, v:1
as[0]: 0x10430240, 1
as in for: 0x10430240, 1
a in for: 0x10434150, v: 1
ValueRececiverInforRange, p: 0x1043415c, v: 1
ValueRececiverInforRange, p: 0x10434164, v: 1
as in for: 0x10430240, 1
a in for: 0x10434150, v: 2
ValueRececiverInforRange, p: 0x10434174, v: 2
ValueRececiverInforRange, p: 0x1043417c, v: 2
as in for: 0x10430240, 1
a in for: 0x10434150, v: 3
ValueRececiverInforRange, p: 0x1043418c, v: 3
ValueRececiverInforRange, p: 0x10434194, v: 3
as in for: 0x10430240, 1
a in for: 0x10434150, v: 4
ValueRececiverInforRange, p: 0x104341a4, v: 4
ValueRececiverInforRange, p: 0x104341ac, v: 4
as in for: 0x10430240, 1
a in for: 0x10434150, v: 5
ValueRececiverInforRange, p: 0x104341bc, v: 5
ValueRececiverInforRange, p: 0x104341c4, v: 5
ValueReceiverInDefer, p: 0x104341cc, v:1
ValueReceiverInDefer, p: 0x104341d4, v:1 結論:通過分析以上地址, 對於value receciver method, 各種調用方式下, 都是對於原值的複製, 也就是說,以副本為receiver調用, 即使是以指標方式調用,也是以*pointer 產生副本後再調用 ;同時也注意到for range中複用了a (for _, a := range as ).-------------------------------------------------------------------------------------- -------------------------------------Pointer Receiver Test--------------------------
package main
import "fmt" import "time"
type A int
func(p *A)PointerReceiver(){
fmt.Printf("PointerReceiver, p: %p, v: %d\n", p, *p) } func(p *A)PointerReceiveringo(){
fmt.Printf("PointerReceiveringo, p: %p, v: %d\n", p, *p) } func(p *A)PointerReceiverinDefer(){
fmt.Printf("PointerReceiverinDefer, p: %p, v: %d\n", p, *p) } func(p *A)PointerReceiverinforRange(){
fmt.Printf("PointerReceiverinforRange, p: %p, v: %d\n", p, *p) } func main() {
var a A = 1 fmt.Printf("main, p: %p, v: %d\n", &a, a) a.PointerReceiver() p := &a p.PointerReceiver() //------------------ go a.PointerReceiveringo() go p.PointerReceiveringo() time.Sleep(3* time.Second) //------------------ defer a.PointerReceiverinDefer() defer p.PointerReceiverinDefer() //------------------ as := [5]A{1,2,3,4,5} fmt.Printf("as[0], p: %p, v: %d\n", &as[0], as[0]) for _, a := range as { fmt.Printf("as in for: p: %p, v: %d\n", &as[0], as[0]) fmt.Printf("a in for: p: %p, v: %d\n", &a, a) a.PointerReceiverinforRange() p := &a p.PointerReceiverinforRange() } }
結果: main, p: 0x10434114, v: 1
PointerReceiver, p: 0x10434114, v: 1
PointerReceiver, p: 0x10434114, v: 1
PointerReceiveringo, p: 0x10434114, v: 1
PointerReceiveringo, p: 0x10434114, v: 1
as[0], p: 0x10430240, v: 1
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 1
PointerReceiverinforRange, p: 0x10434140, v: 1
PointerReceiverinforRange, p: 0x10434140, v: 1
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 2
PointerReceiverinforRange, p: 0x10434140, v: 2
PointerReceiverinforRange, p: 0x10434140, v: 2
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 3
PointerReceiverinforRange, p: 0x10434140, v: 3
PointerReceiverinforRange, p: 0x10434140, v: 3
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 4
PointerReceiverinforRange, p: 0x10434140, v: 4
PointerReceiverinforRange, p: 0x10434140, v: 4
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 5
PointerReceiverinforRange, p: 0x10434140, v: 5
PointerReceiverinforRange, p: 0x10434140, v: 5
PointerReceiverinDefer, p: 0x10434114, v: 1
PointerReceiverinDefer, p: 0x10434114, v: 1
結論: 通過以上地址的分析, 對於pointer receiver method, 在調用時, 不會產生副本, 而是原對象本身的地址;也就是說沒有複製; 在(for _, a := range as) 中的a會被複用。 --------------------------------------------------------------------------------------------------- 參考資料:
https://gobyexample.com/ http://colobu.com/2015/09/07/gotchas-and-common-mistakes-in-go-golang/ http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/ http://waterdudu.github.io/post/go-tips/ https://www.yushuangqi.com/blog/2015/7_things-you-may-not-pay-attation-to-in-go.html http://www.qiukeke.com/2015/05/28/gotchas-and-common-mistakes-in-go-golang.html http://tonybai.com/2015/09/17/7-things-you-may-not-pay-attation-to-in-go/
注意: 此文章只是我個人筆記, 如有錯漏,請一定指正, 共同學習, 我的郵箱: htyu_0203_39@sina.com