同时访问Go中的共享地图

问题描述 投票:0回答:1

我是并发新手,面临以下问题。 我有一个外部 for 循环,用于从数据库获取行。我希望在每个循环中填充顶点分布图

for year := estart; year <= eend; year++ {
        vertexDistribution[year] += degree
}

但是当我从数据库获取的 id 与之前的 id 不同时,我希望 go 例程填充 DegreeDistribution 映射(从 vertexDistribution 映射获取其数据)。

timeStart := time.Now()
for rows.Next() {

    err = rows.Scan(&sourceid, &degree, &estart, &eend)
    if err != nil {
        log.Fatal("Error scanning the rows of degree distribution: ", err)
    }

    if prevsourceid != sourceid && prevsourceid != "" {
        mutex.Lock()
        temp := vertexDistribution
        mutex.Unlock()

        vertexDistribution = make(map[int]int) // clear the map

        go func(temp map[int]int) {
            mutex.Lock()
            defer mutex.Unlock()
            for k, v := range temp {
                if _, ok := degreeDistribution[k]; !ok {
                    degreeDistribution[k] = make(map[int]int)
                }
                degreeDistribution[k][v]++
            }
        }(temp)
    }

    for year := estart; year <= eend; year++ {
        vertexDistribution[year] += degree
    }

    prevsourceid = sourceid
}

mutex.Lock()
defer mutex.Unlock()
for k, v := range vertexDistribution { // covering the last vertexDistribution data
    if _, ok := degreeDistribution[k]; !ok {
        degreeDistribution[k] = make(map[int]int)
    }
    degreeDistribution[k][v]++
}

elapsedTime := time.Since(timeStart)
fmt.Println(elapsedTime.Seconds(), "seconds elapsed getting the degree distribution")

显然我的概念是错误的,有时输出是正确的,有时是错误的,有时我得到致命错误:并发地图迭代和地图写入

我跑了 go run -race main.go 并得到了

==================
WARNING: DATA RACE
Read at 0x00c000192000 by main goroutine:
  reflect.maplen()
      /usr/local/go/src/runtime/map.go:1406 +0x0
  reflect.Value.lenNonSlice()
      /usr/local/go/src/reflect/value.go:1785 +0x1e9
  reflect.Value.Len()
      /usr/local/go/src/reflect/value.go:1774 +0x137
  internal/fmtsort.Sort()
      /usr/local/go/src/internal/fmtsort/sort.go:58 +0x121
  fmt.(*pp).printValue()
      /usr/local/go/src/fmt/print.go:816 +0x1144
  fmt.(*pp).printValue()
      /usr/local/go/src/fmt/print.go:827 +0x2c8f
  fmt.(*pp).printArg()
      /usr/local/go/src/fmt/print.go:759 +0xb84
  fmt.(*pp).doPrintln()
      /usr/local/go/src/fmt/print.go:1221 +0x4b
  fmt.Fprintln()
      /usr/local/go/src/fmt/print.go:304 +0x52
  fmt.Println()
      /usr/local/go/src/fmt/print.go:314 +0x247
  main.main()
      /home/xaris/VScode/HiNode-CockroachDB/main.go:21 +0x1e6

Previous write at 0x00c000192000 by goroutine 22575:
  runtime.mapassign_fast64()
      /usr/local/go/src/runtime/map_fast64.go:93 +0x0
  hinode/models.MultiTable.GetDegreeDistributionConcurrently.func1()
      /home/xaris/VScode/HiNode-CockroachDB/models/multiTable.go:352 +0x184
  hinode/models.MultiTable.GetDegreeDistributionConcurrently.gowrap1()
      /home/xaris/VScode/HiNode-CockroachDB/models/multiTable.go:354 +0x41

Goroutine 22575 (finished) created at:
  hinode/models.MultiTable.GetDegreeDistributionConcurrently()
      /home/xaris/VScode/HiNode-CockroachDB/models/multiTable.go:345 +0x8b6
  main.main()
      /home/xaris/VScode/HiNode-CockroachDB/main.go:19 +0x1d6
==================
==================
WARNING: DATA RACE
Read at 0x00c0000eaca8 by main goroutine:
  reflect.typedmemmove()
      /usr/local/go/src/runtime/mbarrier.go:203 +0x0
  reflect.copyVal()
      /usr/local/go/src/reflect/value.go:2064 +0x5b
  reflect.(*MapIter).Value()
      /usr/local/go/src/reflect/value.go:1961 +0x124
  internal/fmtsort.Sort()
      /usr/local/go/src/internal/fmtsort/sort.go:64 +0x3b6
  fmt.(*pp).printValue()
      /usr/local/go/src/fmt/print.go:816 +0x1144
  fmt.(*pp).printValue()
      /usr/local/go/src/fmt/print.go:827 +0x2c8f
  fmt.(*pp).printArg()
      /usr/local/go/src/fmt/print.go:759 +0xb84
  fmt.(*pp).doPrintln()
      /usr/local/go/src/fmt/print.go:1221 +0x4b
  fmt.Fprintln()
      /usr/local/go/src/fmt/print.go:304 +0x52
  fmt.Println()
      /usr/local/go/src/fmt/print.go:314 +0x247
  main.main()
      /home/xaris/VScode/HiNode-CockroachDB/main.go:21 +0x1e6

Previous write at 0x00c0000eaca8 by goroutine 22577:
  hinode/models.MultiTable.GetDegreeDistributionConcurrently.func1()
      /home/xaris/VScode/HiNode-CockroachDB/models/multiTable.go:352 +0x193
  hinode/models.MultiTable.GetDegreeDistributionConcurrently.gowrap1()
      /home/xaris/VScode/HiNode-CockroachDB/models/multiTable.go:354 +0x41

Goroutine 22577 (finished) created at:
  hinode/models.MultiTable.GetDegreeDistributionConcurrently()
      /home/xaris/VScode/HiNode-CockroachDB/models/multiTable.go:345 +0x8b6
  main.main()
      /home/xaris/VScode/HiNode-CockroachDB/main.go:19 +0x1d6
==================
go concurrency mutex
1个回答
0
投票

“并发映射迭代和映射写入”通常意味着您要迭代映射,然后尝试在该 for 循环中向同一映射添加新键。我没有看到你的代码示例中发生了这种情况,但恐慌应该给你一个行号来调查。

更一般地说,如果您要使用多个例程读取/写入相同的映射,我建议定义安全方法来与这些数据对象交互,然后仅使用这些方法。例如:

type Distribution struct {
    store map[int]int
    mu sync.Mutex
}

func (d *Distribution) Insert(k, v int) {
    d.mu.Lock()
    d.store[k] = v
    d.mu.Unlock()
}

... additional methods for Distribution operations ...

这将迫使您思考哪些操作同时是安全的,哪些操作需要互斥,然后在进入业务逻辑之前实现一次。

此外,如果您只是将它们用于计数器,请查看同步包中的原子计数器。它们可能适合您用例的某些部分。

© www.soinside.com 2019 - 2024. All rights reserved.