Go不正确的结构体初始化?

3
在编程时我遇到了一个问题。当我在goroutine中使用内部结构体的方法时,我无法像这段代码一样看到内部状态。
package main

import (
    "fmt"
    "time"
)

type Inner struct {
    Value int
}

func (c Inner) Run(value int) {
    c.Value = value
    for {
        fmt.Println(c.Value)
        time.Sleep(time.Second * 2)
    }

}

type Outer struct {
    In Inner
}

func (c Outer) Run()  {
    go c.In.Run(42)

    for {
        time.Sleep(time.Second)
        fmt.Println(c.In)
    }
}

func main()  {
    o := new(Outer)
    o.Run()
}

程序打印:

from inner:  {42}
from outer:  {0}
from outer:  {0}
from inner:  {42}
from outer:  {0}
from inner:  {42}
from outer:  {0}
from outer:  {0}

也许这是指针问题,但我不知道如何解决它。
2个回答

3
您代码中最明显的错误是Inner.Run()具有值接收器,这意味着它获取Inner类型的副本。当您修改此内容时,您会修改该副本,而调用者不会在Inner值上看到任何更改。

因此,首先将其修改为具有指针接收器:

func (c *Inner) Run(value int) {
    // ...
}

如果一个方法有指针接收器,那么该方法被调用时传递给它的是值的地址(指针)。在方法内部,您将修改所指向的值,而不是指针。指针指向调用方处存在的同一值,因此修改的是相同的值(而不是副本)。
仅通过这种更改可能会使您的代码正常工作。但是,您的程序输出是不确定的,因为您从一个goroutine修改了一个变量(字段),并且您也从另一个goroutine读取了此变量,因此必须以某种方式同步对此字段的访问。
同步访问的一种方法是使用sync.RWMutex
type Inner struct {
    m     *sync.RWMutex
    Value int
}

当您创建Outer值时,请初始化此互斥锁:
o := new(Outer)
o.In.m = &sync.RWMutex{}

或者一行代码:

o := &Outer{In: Inner{m: &sync.RWMutex{}}}

Inner.Run()中访问Inner.Value字段时需要加锁:

func (c *Inner) Run(value int) {
    c.m.Lock()
    c.Value = value
    c.m.Unlock()

    for {
        c.m.RLock()
        fmt.Println(c.Value)
        c.m.RUnlock()
        time.Sleep(time.Second * 2)
    }
}

当您访问Outer.Run()中的字段时,还必须使用锁:

func (c Outer) Run() {
    go c.In.Run(42)

    for {
        time.Sleep(time.Second)
        c.In.m.RLock()
        fmt.Println(c.In)
        c.In.m.RUnlock()
    }
}

注意:

你的示例只在 Inner.Run 的开头改变了一次 Inner.Value。因此,上述代码执行了大量不必要的锁定/解锁操作,如果 Outer.Run() 中的循环等待值被设置,然后两个goroutine都可以在没有锁定的情况下读取该变量,那么这些操作可以被移除。通常,如果变量也可能在以后的时间被更改,则需要在每次读/写时执行上述所述的锁定/解锁操作。


谢谢你的决心!现在我明白它是如何工作的。现在代码运行得很好。 - Антон Афанасьев

3

解决问题的最简单方法是在Run函数中使用指针接收器:

func (c *Inner) Run(value int) {
    out = make(chan int)
    c.Value = value
    for {
        fmt.Println(c.Value)
        time.Sleep(time.Second * 2)
    }
}

但另一种解决方案是使用外部通道,可以向其发送Inner结构体的值:

func (c Inner) Run(value int) {
    out = make(chan int)
    c.Value = value
    for {
        fmt.Println(c.Value)
        time.Sleep(time.Second * 2)
        out <- c.Value
    }
}

然后在另一个 goroutine 中接收发送的值:

for{
    go func() {
        c.In.Run(42)
        <-out
        fmt.Println(out)
    }()
    time.Sleep(time.Second)       
}

这是完整的代码:
package main

import (
    "fmt"
    "time"
)

type Inner struct {
    Value int
}

var out chan int

func (c Inner) Run(value int) {
    out = make(chan int)
    c.Value = value
    for {
        fmt.Println(c.Value)
        time.Sleep(time.Second * 2)
        out <- c.Value
    }
}

type Outer struct {
    In Inner
}

func (c Outer) Run()  {    

    for{
        go func() {
            c.In.Run(42)
            <-out
            fmt.Println(out)
        }()
        time.Sleep(time.Second)       
    }
}

func main()  {
    o := new(Outer)
    o.Run()
}

https://play.golang.org/p/Zt_NAsM98_


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