去不正确的struct初始化?

时间:2016-05-05 06:19:32

标签: pointers go struct concurrency goroutine

编码时我遇到了问题。当我在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 个答案:

答案 0 :(得分: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.Value的开头更改Inner.Run一次。所以上面的代码执行了很多不必要的锁定/解锁,如果Outer.Run()中的循环等待直到设置了值,那么可以删除这些锁定/解锁,之后两个goroutine都可以在不锁定的情况下读取变量。通常,如果变量也可以在以后更改,则每次读/写时都需要进行上述锁定/解锁。

答案 1 :(得分: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)
    }
}

但另一种解决方案是使用out通道向您发送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_