在for-range循环中,指针和值切片之间的区别

3
请查看以下代码片段:
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)
}

链接在这里:https://play.golang.org/p/cdryPmyWt5 上面的代码将检查指针和值在for循环中的差异,同时还使用了go语句。对于代码:
values := []field{{"one"},{"two"},{"three"}}
for _, v := range values {
    go v.print()
}

我们知道控制台会打印三三三作为结果,因为for循环在goroutine开始执行之前已经运行到了结尾,将v写入切片的最后一个元素。但是指针呢?

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

似乎输出了一 二 三,为什么?
谢谢。
1个回答

7

A: 在调用函数之前,参数将被评估。在评估之后,调用的函数会按值传递调用的参数,随后开始执行,因此:

第一个go v.print()是第二个go (*field).print(v)的语法糖,并且
第二个go v.print()是第二个go (*field).print(v)的语法糖。

如果第一个for循环在goroutine启动之前完成,则&v对于所有调用都是相同的,这三个调用都是相同的。请参见代码2,在第一个循环后添加time.Sleep(100)或使用go func(v field) { v.print() }(v)Go Playground(带有sync.WaitGroup的代码3).
此外,此处存在数据竞争(请参见B)。

对于这里的第二个go (*field).print(v)v是指针,三个goroutine的参数在调用print之前进行了评估,具有三个不同的地址

1- 在Go Playground上尝试此操作:

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 {
        fmt.Println(&v)
        go (*field).print(&v) //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 {
        fmt.Println(v)
        go (*field).print(v) //go v.print()
    }

    time.Sleep(time.Second)
}

输出:

use values:
&{one}
&{two}
&{three}
three
three
three

use pointers:
&{one}
&{two}
&{three}
two
one
three

2- 在Go Playground上尝试以下操作:

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 {
        fmt.Println(&v)
        go v.print() //go (*field).print(&v) //
        time.Sleep(100)
    }

    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 {
        fmt.Println(v)
        go v.print() //go (*field).print(v) //
    }

    time.Sleep(time.Second)
}

输出:

use values:
&{one}
one
&{two}
two
&{three}
three

use pointers:
&{one}
&{two}
&{three}
one
two
three

B: 你的代码存在数据竞争问题,尝试使用go build -race 命令 编译你的代码,然后运行生成的文件,如果出现WARNING: DATA RACE警告信息:

输出结果:

use values:
==================
WARNING: DATA RACE
Read at 0x00c042030210 by goroutine 6:
  runtime.convT2E()
      Go/src/runtime/iface.go:155 +0x0
  main.(*field).print()
      .../m.go:14 +0x6c

Previous write at 0x00c042030210 by main goroutine:
  main.main()
      .../m.go:22 +0x1c3

Goroutine 6 (running) created at:
  main.main()
      .../m.go:23 +0x204
==================
two
three
three

use pointers:
one
two
three
Found 1 data race(s)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接