【问题标题】:Go improper struct initialization?进行不正确的结构初始化?
【发布时间】:2023-03-16 17:55:01
【问题描述】:

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

可能是指针问题,但我不知道如何解决。

【问题讨论】:

    标签: pointers go struct concurrency goroutine


    【解决方案1】:

    您的代码中最明显的错误是Inner.Run() 有一个值接收器,这意味着它获得了Inner 类型的副本。当你修改这个时,你修改了副本,调用者不会看到 Inner 值的任何变化。

    所以先把它修改成一个指针接收器:

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

    如果一个方法有一个指针接收器,则调用该方法的值的地址(指针)将被传递给该方法。在方法内部,您将修改 pointed 值,而不是指针。指针指向调用者的相同值,因此修改了相同的值(而不是副本)。

    仅此更改可能会使您的代码正常工作。但是,您的程序的输出是不确定的,因为您从一个 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.Value 字段时锁定Inner.Run()

    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()中的循环会等待直到值被设置,然后两个goroutines可以在没有锁定的情况下读取变量,则可以删除这些锁/解锁。一般来说,如果变量也可以在以后更改,则每次读/写都需要上述锁定/解锁。

    【讨论】:

    • 感谢您的解决!现在我明白它是如何工作的了。现在代码运行良好。
    【解决方案2】:

    解决问题的最简单方法是在 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_

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-20
      • 1970-01-01
      • 2011-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-03
      • 2016-01-18
      相关资源
      最近更新 更多