dongtuoleng8624 2016-09-17 12:25
浏览 68
已采纳

范围循环中指针和值切片之间的差异

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 goroutines 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.

  • 写回答

1条回答 默认 最新

  • dongxi3209 2016-09-17 12:45
    关注

    A: The parameters are evaluated before the function is called. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution, so:

    The first go v.print() is syntactic sugar for go (*field).print(&v) and
    the second go v.print() is syntactic sugar for go (*field).print(v).

    If first for loop finishes before goroutines start, &v is same for the calls and these three calls is all the same. See code 2 by adding time.Sleep(100) after go v.print() to first loop. or with go func(v field) { v.print() }(v) on The Go Playground (Code 3 with sync.WaitGroup).
    Also, you have data race here (see B).

    And for the second go (*field).print(v) here v is pointer and three goroutines parameter's evaluated before calling print and have three different addresses.

    1- Try this on The 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)
    }
    

    output:

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

    2- Try this on The 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)
    }
    

    output:

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

    B: You have data race, try go build -race your code, then run generated file, WARNING: DATA RACE:

    output:

    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)
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥20 iOS绕地区网络检测
  • ¥15 python验证码滑块图像识别
  • ¥15 根据背景及设计要求撰写设计报告
  • ¥15 QT6颜色选择对话框显示不完整
  • ¥20 能提供一下思路或者代码吗
  • ¥15 用twincat控制!
  • ¥15 请问一下这个运行结果是怎么来的
  • ¥15 单通道放大电路的工作原理
  • ¥30 YOLO检测微调结果p为1
  • ¥15 DS18B20内部ADC模数转换器