這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。注意: 此文章只是我的個人筆記,如有謬誤,錯誤, 請一定指出!
for range 問題
http://stackoverflow.com/questions/30577212/go-for-range-slice-and-goroutine-method-invocation-the-logic-behind
package mainimport ( "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 mainimport ( "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 thevalue of afield item. Becausev is addressable, it is automatically referenced as the pointer receiver for theprint() method. Sov.print() is using the address ofv 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 afield value. When you callv.print() in this case, you are operating on the value thatv points to, which is stored indata, 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 = 1fmt.Printf("main, p: %p, v: %d\n", &a, a)a.ValueReceiver()p := &ap.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 := &apf.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 = 1fmt.Printf("main, p: %p, v: %d\n", &a, a)a.PointerReceiver()p := &ap.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 := &ap.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.htmlhttp://www.qiukeke.com/2015/05/28/gotchas-and-common-mistakes-in-go-golang.htmlhttp://tonybai.com/2015/09/17/7-things-you-may-not-pay-attation-to-in-go/
注意: 此文章只是我個人筆記, 如有錯漏,請一定指正, 共同學習, 我的郵箱: htyu_0203_39@sina.com