代码之家  ›  专栏  ›  技术社区  ›  David Chen

要在for循环中使用goroutine,为什么在指向struct而不是structs本身的指针上迭代

  •  1
  • David Chen  · 技术社区  · 6 年前

    背景

    我在看50种颜色的围棋,特别是 Iteration Variables and Closures in "for" Statements ,我要从中摘录一段。

    错误的

        package main
    
        import (  
            "fmt"
            "time"
        )
    
        type field struct {  
            name string
        }
    
        func (p *field) print() {  
            fmt.Println(p.name)
        }
    
        func main() {  
            data := []field{{"one"},{"two"},{"three"}}
    
            for _,v := range data {
                go v.print()
            }
    
            time.Sleep(3 * time.Second)
            //goroutines print: three, three, three
        }
    

    对的

    变化 []field{{"one"},{"two"},{"three"}} []*field{{"one"},{"two"},{"three"}} one , two three 将按顺序打印。

    我的思维过程

    1. 在不正确的情况下, go v.print() 被替换为 go (&v).print() 由编译器执行,因为 print() 在指针接收器上定义。

    2. 在生成的goroutine被执行之前,运行时只知道goroutine应该执行 打印() ,但不知道应将哪个实例作为接收器传递的指针。

    3. 当派生的goroutine被执行时,for循环很可能已经终止,所以当我们想决定哪个值应该作为接收方传递时,我们得到 v ,它在整个循环中共享,并在每次迭代中更新,因此我们传递 data 作为接受者 打印() ,这就是为什么我们得到3 印刷的。

    问题

    对我来说,改变 []field []*field 只允许编译器跳过步骤1,但不更改步骤2和步骤3,所以我不知道为什么这样可以解决问题。

    我想我的思维过程中一定有一些缺陷,我很感激你的建议。

    更新

    我碰巧看到另一个正确的实现 here ,我想我可能知道我的思维过程中哪里出错了。

    data := []field{{"one"}, {"two"}, {"three"}}
    
    for i := range data {
        go data[i].print()
    }
    

    问题是,要传递给 打印() 作为接收器,在步骤2而不是步骤3中确定。这意味着在不正确的版本中,我们在每次迭代中传递相同的地址,但是它指向的内容( 数据 )在每次迭代中更新。但是,在正确的版本中,指针被传递到 打印() 作为接收器,指向 field . 这同样适用于使用索引的情况。

    1 回复  |  直到 6 年前
        1
  •  1
  •   Mostafa Solati    6 年前

    您的接收器是一个poniter,您必须将字段片段定义为指针这就是为什么此代码有效

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

    如果将接收器更改为非指针,则代码也可以工作。

    func (p field) print() {
        fmt.Println(p.name)
    }
    
    data := []field{{"one"}, {"two"}, {"three"}}