Golang for loop closure?


(何晨) #1

here is a snippet: https://play.golang.org/p/cKrC0oy7FP

package main

import (
	"fmt"
	"time"
)

type field struct {
    name string
}

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

func TestClosure() {

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

    for _, v := range data {
        go v.print()
    }

    time.Sleep(3 * time.Second)
}

func TestClosure1() {

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

    for _, v := range data {
        go v.print()
    }

    time.Sleep(3 * time.Second)
}

func TestClosure2() {

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

    for _, v := range data {
        go func(){
		v.print()
	}()
    }

    time.Sleep(3 * time.Second)
}

func main(){
	TestClosure() // two one three (one possible result)
	fmt.Println("----")
	TestClosure1() // three three three (always)
	fmt.Println("----") 
	TestClosure2()	// three three three (always)
}

The only difference between TestClosure() and TestClosure1() is data, a array of field or array of *field.
The difference between TestClosure1() and TestClosure2() is the way goroutine started.

my question is why the TestClosure can get the right answer but TestClsoure1 cant?

and if i change the way TestClosure start goroutine like below, TestClosure get wrong answer like TestClosure1, Why?

go func(){
    v.printf()
}

(Dave Cheney) #2

What you see is two independent effects.

The first is

Which relies on the way the expression passed to the go statement is evaluated. The full spec is here

https://golang.org/ref/spec#Go_statements

but the short version is; the go statement evaluates it’s arguments which must resolve to a function or method invocation. So what you have in TestClosure1 is go evaluates v's print method, and calls it in a goroutine.

The key here is v is evaluated before the goroutine is invoked. To understand why this is important we need to look at the problem posed in TestClosure2

In this example the expression being evaluated by the go statement is an anonymous function which closes over the value of v. v is defined in the scope of the for statement block and is +overwritten+ on each loop iteration.

So, why is v overwritten? To understand why you need to realise that the range syntax is syntatic sugar over a traditional for loop, rewriting TestClosure2 to make this clear gives something like

var v field
for i := 0; i < len(data); i++ {
    v = data[i]
    go func() { 
          v.print()
    }()
}

Hopefully it’s clear that v is being overwritten on each loop. What may not be clear is that v is being lexically captured by the anonymous function. Let me rewrite the example to show this a little clearer (whist still being the same code)

var v field
for i := 0; i < len(data); i++ {
    v = data[i]
    go func(p *field) { 
          p.print()
    }(&v)
}

In the rewritten example you can see there is only one value of v, declared before the for loop. On each iteration the value assigned to v is updated, and the address of v is passed to the anonymous function.

So, each goroutine is going to receive a pointer to the same value (we know it’s the same, because it is defined outside the scope of the for loop). That is why they all print the same answer.

You can read more about this here

https://golang.org/doc/faq#closures_and_goroutines

Oh, and as a bonus, this is also a data race.

https://blog.golang.org/race-detector


(何晨) #3

if so, why TestClosure can get the right evaluated value (array of *field) while TestClosure1(array of field) can’t ?

With my understand, the spec says if it is a expression that passed to go statment, then the statement must be evaluate first.

The function value and parameters are evaluated as usual in the calling goroutine, but unlike with a regular call, program execution does not wait for the invoked function to complete.

so TestClosure and TestClosure1 should both output the correct answer cause v has been evaluated, and when the goroutine truly call the print func, value v should have the correct value under both circumstance.

BTW the TestClosure2 part is great, Appreciate!


(Dave Cheney) #4

I don’t see any material difference between TestClosure and TestClosure1.

Can you please

  • write down what you did.
  • write down what you got when you did it.
  • write down what you expected to get, if it wasn’t what you got.

(何晨) #5

sorry the difference is trivial, actually TestClosure got a array of *field while TestClsoure1 got a array of field, just this small.

and the result has been written on the original post.

should have name the func more more obvious.


(Dave Cheney) #6

Ok, sorry I was mistaken in saying that TestClosure and TestClosure1 are identical in operation. This was wrong.

What you’ve discovered is another piece of syntactic sugar, whereby if you call a method that takes a pointer receiver on a value, then Go will automatically take the address of the value and use that as the receiver.

This has a subtle effect on your program. When the go statement evaluates v.print() if it needs a pointer receiver but does not have one, it takes the address of v, which as we saw before causes all of the go routines to work with the same copy of v.

If the go statement finds that it already has a pointer receiver, then it can use it directly as the receiver.

You can see this here with a modified version of your program that prints the address of field's receiver.

https://play.golang.org/p/wUXsuzdeI4

You can see that in TestClosure the address is different for the three invocations, it is in fact the address of the elements of []*field.


(system) #7

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.