V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
IsaacYoung
V2EX  ›  Go 编程语言

菜鸟问一个关于 goroutine 和闭包的问题

  •  
  •   IsaacYoung · 2018-10-26 20:33:53 +08:00 · 2326 次点击
    这是一个创建于 2209 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看到这样一段代码,有大佬给解释下pointer receiver对 goroutine 执行的影响,或者说解释下这段代码三个函数表现行为的具体原因,谢谢了🙏

    playground 地址: 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()
    	fmt.Println("----")
    	TestClosure1()
    	fmt.Println("----")
    	TestClosure2()	
    }
    
    
    6 条回复    2018-10-27 13:35:27 +08:00
    wqlin
        1
    wqlin  
       2018-10-26 20:52:58 +08:00   ❤️ 1
    首先看 TestClosure2 ; 犯了常见错误,可以参考: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
    应该改成:
    ```
    func TestClosure2() {

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

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

    time.Sleep(3 * time.Second)
    }
    ```
    理解了 TestClosure2 之后来看 TestClose1。这里要理解 function receiver ;可以参考: https://tour.golang.org/methods/4。关键是这句话
    >
    With a value receiver, the Scale method operates on a copy of the original Vertex value. (This is the same behavior as for any other function argument.)
    >
    所以要实现同样的效果,可以改成:
    ```
    func TestClosure1() {

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

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

    time.Sleep(3 * time.Second)
    }
    ```

    最后放 playground: https://play.golang.org/p/2C71XfJm2SI
    wqlin
        2
    wqlin  
       2018-10-26 21:00:48 +08:00
    @wqlin #1
    TestClosure1 也可以改成:
    ```
    func TestClosure1() {

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

    for i := range data {
    go data[i].print()
    }

    time.Sleep(3 * time.Second)
    }
    ```
    实现同样的效果
    icexin
        3
    icexin  
       2018-10-26 21:35:24 +08:00   ❤️ 1
    代码改成这样你就能明白了
    ``` go
    package main

    import (
    "fmt"
    "time"
    )

    type field struct {
    name string
    }

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

    func TestClosure() {

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

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

    time.Sleep(3 * time.Second)
    }

    func TestClosure1() {

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

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

    time.Sleep(3 * time.Second)
    }

    func TestClosure2() {

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

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

    time.Sleep(3 * time.Second)
    }

    func main(){
    TestClosure()
    fmt.Println("----")
    TestClosure1()
    fmt.Println("----")
    TestClosure2()
    }

    ```

    `go print(v)`会在执行函数前把参数(v)以值拷贝的方式固定住
    `go func(){print(v)}` 会在执行的时候再获取参数(v)的值
    reus
        4
    reus  
       2018-10-26 21:35:46 +08:00
    for 语句的循环变量,需要传入闭包时,应该先复制一份,因为每次循环都是使用同一个变量的

    加 v := v 就可以复制了
    cloudyplain
        5
    cloudyplain  
       2018-10-27 07:59:01 +08:00
    TestClosure,指针
    TestClosure1,v.print() 等价 (&v).print,print 使用的是相同的地址,但指针内容在循环后被改变
    TestClosure2, 闭包常见问题
    Appla
        6
    Appla  
       2018-10-27 13:35:27 +08:00
    https://golang.org/ref/spec#Go_statements
    ```
    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.
    ```
    试试把 time.Sleep(1 * time.Second)加到执行 go 语句的后面看看
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3838 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 00:14 · PVG 08:14 · LAX 16:14 · JFK 19:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.