differences between pointer and value slice in for-range loop

719 views
Skip to first unread message

Fei Ding

unread,
Sep 16, 2016, 11:30:14 PM9/16/16
to golang-nuts
Hi:

Please check this code snippet:

package main

import (  
    "fmt"
    "time"
)

type field struct {  
    name string
}

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

func main() {
    fmt.Println("use values:")

    // use values in range loop and go rountines
    values := []field{{"one"},{"two"},{"three"}}
    for _, v := range values {
        go v.print()
    }

    time.Sleep(time.Second)

    fmt.Println()
    fmt.Println("use pointers:")

    // use pointers in range loop and go rountines
    poniters := []*field{{"one"},{"two"},{"three"}}
    for _, v := range poniters {
        go v.print()
    }

    time.Sleep(time.Second)
}

Link here: https://play.golang.org/p/cdryPmyWt5

The code above is going to check the differences between pointers and values in a for loop, while go statement is also used at the same time. For code:

    values := []field{{"one"},{"two"},{"three"}}
    for _, v := range values {
        go v.print()
    }

we know that the console will print three three three as result, because for loop runs into its end before go routines start executing, which write v as the last element of the slice. But what about pointers? 

    poniters := []*field{{"one"},{"two"},{"three"}}
    for _, v := range poniters {
        go v.print()
    }

It seems to print one two three, why?

Thanks.







Marvin Stenger

unread,
Sep 17, 2016, 7:18:03 AM9/17/16
to golang-nuts
Because print() is called on different pointer values.

Marvin Renich

unread,
Sep 17, 2016, 2:04:13 PM9/17/16
to golang-nuts
* Fei Ding <fdin...@gmail.com> [160916 23:30]:
> Link here: https://play.golang.org/p/cdryPmyWt5
>
> The code above is going to check the differences between pointers and
> values in a for loop, while go statement is also used at the same time. For
> code:
>
> values := []field{{"one"},{"two"},{"three"}}
> for _, v := range values {
> go v.print()
> }
>
> we know that the console will print *three three three* as result, because
> for loop runs into its end before go routines start executing, which write
> *v* as the last element of the slice. But what about pointers?
>
> poniters := []*field{{"one"},{"two"},{"three"}}
> for _, v := range poniters {
> go v.print()
> }
>
> It seems to print* one two three*, why?

Try running your example with the race detector. Then try commenting
out, on separate trials, each of the two cases, again running under the
race detector.

The second case runs without a race, but the first does not.

To understand why, you must carefully analyze what is happening
according to the Go specification
(https://golang.org/ref/spec#Go_statements).

In the both cases, the variable v is reused for all iterations of the
for loop; that is, every iteration of the loop uses the same variable v,
but with a different value for each iteration.

Now, in the first case, v is of type field. The go statement evaluates
the function value and parameters. Evaluating the function value means,
essentially, getting a pointer to the function to be called (this is a
simplification, but is accurate enough for this analysis); it does not
yet call the function.

A method call can be thought of as a function call with the receiver as
an implied first argument. So the first, implied, argument is evaluated
as &v (because, according to the spec, x.m() is shorthand for (&x).m()
in this scenario). The go statement creates a new goroutine with this
particular function call, with the address of v as the first argument,
to be executed at the go scheduler's discretion.

Next, the for loop assigns the next element from the slice values to the
variable v. This is the data race. There is no guarantee that the
scheduler will wait for the sleep statement after the loop to start
executing the first goroutine, and in fact it didn't on one run on my
system; I got a result of two three three. There is no synchronization
between the assignment to v by the for loop and the reference of v.name
in v.print, so you have a data race.

In the second case, v is of type *field. The go statement evaluates the
first, implied, argument as the current value of v (the first pointer in
the slice). The go statement creates a goroutine with a call to the
print method with that value as the argument.

When the for loop assigns the next element from the slice to v, it is
not changing anything that is being referenced by the go routine, so
there is no data race (and the values printed are taken from successive
slice elements).

...Marvin

Fei Ding

unread,
Sep 18, 2016, 2:58:55 AM9/18/16
to Marvin Renich, golang-nuts
Thanks, Marvin, I've learned a lot from your reply. And, I've written more code, like:

a, b, c := 1, 2, 3
slice1 := []int{a, b, c}
for _, n := range slice1 {
go func(n *int) {fmt.Println(*n)}(&n)
}

 It seems that pass n's address in the code above will make a data race, which you have already explained why. But still, I have another question: If the coder do really want the addresses of elements in slice, how to do it correctly? 

Thanks.




--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/yxhNl3Rnl6Y/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Message has been deleted

Marvin Stenger

unread,
Sep 18, 2016, 7:14:51 AM9/18/16
to golang-nuts, mr...@renich.org

Marvin Renich

unread,
Sep 19, 2016, 10:15:06 AM9/19/16
to golang-nuts
* Fei Ding <fdin...@gmail.com> [160918 02:58]:
> Thanks, Marvin, I've learned a lot from your reply. And, I've written more
> code, like:
>
> a, b, c := 1, 2, 3
> > slice1 := []int{a, b, c}
> > for _, n := range slice1 {
> > go func(n *int) {fmt.Println(*n)}(&n)
> > }
>
>
> It seems that pass *n's address *in the code above will make a data race,
> which you have already explained why. But still, I have another question:
> If the coder do really want the addresses of elements in slice, how to do
> it correctly?

* Marvin Stenger <marvin.s...@gmail.com> [160918 07:12]:
> https://play.golang.org/p/9LQMDrDIOv should work.

Marvin Stenger gives two good solutions in the above link. I will
explain what each does and why you might use one over the other.

for i, _ := range values {
go values[i].print()
}

In this case, the expression values[i].print is evaluated to determine
the function to be scheduled, so the function that is scheduled is the
print method with an implied first argument of &values[i]. This
expression is evaluated prior to scheduling, so neither the variable
values nor the variable i are referenced in the goroutine. However, the
value of &values[i] is a pointer into the backing array for the slice,
which will be important in choosing which solution to use.

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

In the second case, although the same v is reused for all iterations of
the loop, a new w is created each time through the loop. The assignment
to v at the beginning of the loop makes a copy of the slice element and
puts it in v. Then it is copied a second time and stored in w. (The
compiler may be able to optimize the generated code to avoid making two
copies.) Now the function call that is scheduled is a call to the print
method with an implied first argument of &w. Because each iteration of
the loop uses a new variable w, each goroutine has its own copy of the
slice element associated with the iteration of the loop in which the
goroutine was created.

The primary consideration in choosing between these two solutions is
whether or not the goroutine can safely access the slice element in the
backing array. If it can, use the first solution. If it cannot, either
because the backing array might be modified or because the element
itself is not safe to reference concurrently, then use the second
solution. I would probably modify the second solution like this:

for i, _ := range values {
var w = values[i]
go w.print()
}

This doesn't rely on the compiler's ability to optimize copying the
element twice into copying it only once, and it is just as clear to the
next programmer who reads your code.

...Marvin

Reply all
Reply to author
Forward
0 new messages