【问题标题】:Why is this map empty when I populate it in a Goroutine?当我在 Goroutine 中填充它时,为什么这个映射是空的?
【发布时间】:2016-06-27 10:36:56
【问题描述】:
type driver struct {
    variables map[string]string
}

var Drivers []driver

func main() {

    driver := driver{
        variables: make(map[string]string),
    }
    Drivers = append(Drivers, driver)

    driver.variables = make(map[string]string) // Commenting this line makes it work, too

    done := make(chan bool) 
    go driver.populate(done)

    <-done

    fmt.Print(Drivers[0].variables)
}

func (this *driver) populate(done chan bool) {
    time.Sleep(500 * time.Millisecond)
    this.variables["a"] = "b"
    done <- true
}

我预计:

map[a:b]

实际结果:

map[]

Playground

【问题讨论】:

    标签: go struct reference slice goroutine


    【解决方案1】:

    问题很简单:你有一片drivers:

    var Drivers []driver
    

    请注意,Drivers 是某种结构类型的切片,而不是指针切片!

    当你附加一些东西(或者你给它的一个元素赋值)时:

    Drivers = append(Drivers, driver)
    

    这会复制附加(或分配)的值!因此,当您稍后执行此操作时:

    driver.variables = make(map[string]string)
    

    它将新的映射值设置为driver.variables,但这与存储在Drivers(更准确地说是Drivers[0])中的值不同。

    稍后您填充driver.variables,但您打印Drivers[0].variables。它们是 2 个不同的 struct 值,具有 2 个不同的 map 值。 Goroutine 在这里不起作用(它们已正确同步,因此无论如何它们都不应该)。

    你会打印driver.variables

    fmt.Print(driver.variables)
    

    你会看到(在Go Playground上试试):

    map[a:b]
    

    如果你注释掉这一行:

    driver.variables = make(map[string]string) // Commenting this line makes it work, too
    

    它会起作用,只是因为即使你有 2 个结构值,它们也有相同的映射值(相同的映射头指向相同的映射数据结构)。

    如果您在结构值Drivers[0] 上调用driver.populate()(并坚持打印Drivers[0].variables),也可以使其工作:

    go Drivers[0].populate(done)
    
    // ...
    
    fmt.Print(Drivers[0].variables)
    

    Go Playground 上试试这个。

    如果Drivers 是一个指针切片,你也可以让它工作:

    var Drivers []*driver
    
    // ...
    
    driver := &driver{
        variables: make(map[string]string),
    }
    

    因为driverDriver[0] 将是同一个指针(您将只有一个结构值和一个映射值,因为初始映射不再可访问)。在Go Playground 上试试这个。

    【讨论】:

    • 这是正确的答案:在这种情况下,goroutines 是无关紧要的:play.golang.org/p/Eh3gPbFWPL 显示只有主 goroutine 的问题;理解结构是复制的,但指针(和引用类型,如map)是共享的,这是导致问题的原因。
    【解决方案2】:

    使用 goroutine 版本没有得到未初始化的 map 的原因是当 main 函数返回时,程序退出:它不等待其他(非主)goroutine 完成 .请注意 main 函数本身就是一个 goroutine。

    因此,即使您使用以下方式初始化地图:

    driver.variables = make(map[string]string)
    

    这并不意味着您实际上填充了值,您只是初始化了一个哈希映射数据结构并返回一个指向它的映射值。

    Map 类型是引用类型,如指针或切片,因此 上述 m 的值为 nil;它不指向已初始化的地图。零 map 在读取时表现得像一个空 map,但尝试写入一个 nil map 会导致运行时恐慌;不要那样做。初始化一个 map,使用内置的make函数。

    如果您首先删除 go 关键字,它将初始化 driver.variables 映射。但是因为它在同一个线程(主线程)中运行,并且您首先在populate 函数上设置了时间延迟,所以它会初始化地图,然后填充它。

    【讨论】:

    • 我不明白。 populate() goroutine 不是在 main goroutine 执行 time.Sleep(1000ms) 时运行吗?
    • 在彼得的建议之后,我更新了我的问题。我认为您的解释不再适用,但行为仍然相同。
    • 是的,它正在运行,但是如果你在另一个 goroutine 上调用 populate 函数,driver.variables = make(map[string]string) 将被重新初始化。
    【解决方案3】:

    我最好使用频道而不是sleeps:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    type driver struct {
        variables map[string]string
    }
    
    var Drivers []driver
    
    func main() {
        driver := driver{
            variables: make(map[string]string),
        }
        Drivers = append(Drivers, driver)
    
        done := make(chan bool)
        go driver.populate(done)
        <-done // wait for goroutine to complete
    
        fmt.Print(Drivers[0].variables)
    }
    
    func (this *driver) populate(done chan bool) {
        time.Sleep(500 * time.Millisecond)
        this.variables["a"] = "b"
        done <- true
    }
    

    Playground

    【讨论】:

    • 好主意。您的版本仍然显示相同的问题,并消除了对竞争条件的所有疑问。我更新了我的问题。
    • @AndreKR 是的,确实,我刚刚注意到它与 goroutine 无关。您使用的是结构切片,而不是指针切片。它适用于指针切片:play.golang.org/p/R8Vkwh_J9H
    猜你喜欢
    • 2019-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-27
    • 1970-01-01
    • 2012-05-11
    相关资源
    最近更新 更多