【问题标题】:Golang Data Race, with 66 exit statusGolang Data Race,退出状态为 66
【发布时间】:2016-10-11 20:03:03
【问题描述】:

我有以下代码,并且我正在进行数据竞争。 Round函数定期检查运行函数删除地图内容 正如我在这里读到的:Is it safe to remove selected keys from Golang map within a range loop?

从地图中删除数据是安全的,但我有数据竞争

package main

import (
    "fmt"
    "sync"
    "time"
)

type City struct {
    ID string
}

type Map struct {
    sync.RWMutex
    Data map[string]City
}

var done = make(chan struct{})

func (m *Map) Round() {
    for {
        select {
        case <-time.After(2 * time.Second):
            for i, v := range m.Data {
                fmt.Println("-----", v)
                delete(m.Data, i)
            }
        case <-done:
            println("bye")
            break
        }
    }
}

func (m *Map) Add(id string, h City) {
    m.Lock()
    m.Data[id] = h
    m.Unlock()
}

func main() {
    m := Map{}
    m.Data = make(map[string]City)

    m.Data["Ottowa"] = City{"Canada"}
    m.Data["London"] = City{"GB"}
    m.Data["malafya"] = City{"malafya"}

    go m.Round()

    for i := 0; i < 4; i++ {
        go func() {
            time.Sleep(2 * time.Second)
            go m.Add("uz", City{"CityMakon"})
            go m.Add("uzb", City{"CityMakon"})
        }()
    }
    time.Sleep(5 * time.Second)
    done <- struct{}{}
}

输出:

----- {Canada}
----- {GB}
----- {malafya}
==================
WARNING: DATA RACE
Write by goroutine 12:
  runtime.mapassign1()
      /usr/lib/golang/src/runtime/hashmap.go:411 +0x0
  main.(*Map).Add()
      /home/narkoz/elixir/round.go:37 +0xaa

Previous write by goroutine 6:
  runtime.mapdelete()
      /usr/lib/golang/src/runtime/hashmap.go:511 +0x0
  main.(*Map).Round()
      /home/narkoz/elixir/round.go:26 +0x3a9

Goroutine 12 (running) created at:
  main.main.func1()
      /home/narkoz/elixir/round.go:54 +0x8c

Goroutine 6 (running) created at:
  main.main()
      /home/narkoz/elixir/round.go:49 +0x2af
==================
----- {CityMakon}
----- {CityMakon}
Found 1 data race(s)
exit status 66

但是当我将 map 的值类型更改为 int 或 string 时,没有数据竞争。

你推荐什么解决方案?

【问题讨论】:

  • 我认为影响结果的类型是巧合(但也许知道的人会回答)。您还需要锁定地图迭代并在 Round() 中删除。
  • 谢谢,所以我已经用 RLock 和 RUnlock 包裹了范围循环,这消除了第二次数据竞争
  • 比赛检测器告诉您比赛发生的位置准确。你打电话给delete 没有拿锁。
  • @maksadbek 因为在那个循环中有一个delete,你真的需要一个Lock,而不仅仅是一个RLock

标签: go


【解决方案1】:

更新:

但是当我将 map 的值类型更改为 int 或 string 时,没有数据竞争。

我进一步测试了您的代码。将映射的值类型更改为 int 或 string 会继续产生竞争条件。尝试在你的 shell 上的 while 循环中运行它,你会明白我的意思:

$ while true; do go run -race main.go; done

值类型之间不应有任何差异。


根据竞态检测器的报告,有两种不同的竞态条件。第一场比赛(您已修复)发生在第 54 行的读取(i)和第 51 行的写入(i)之间。发生这种情况是因为您的 goroutine 闭包包含对 i 的引用,这由 main goroutine 中的 for 循环更改。您可以通过摆脱println("&gt;&gt;", i) 或将i 传递到您的闭包中来解决它,如下所示:

for i := 0; i < 4; i++ {
  go func(index int) {
    time.Sleep(2 * time.Second)
    println(">>", index)
    go m.Add("uz", City{"CityMakon"})
    go m.Add("uzb", City{"CityMakon"})
  }(i)
}

第二个竞争条件发生在第 37 行的赋值 (m.Data[id] = h) 和第 25 行的删除 (delete(m.Data, i)) 之间。竞态检测器将此标记为竞态条件,因为它不能保证您的代码受到Happen Before 约束。您可以通过以下任一方式解决此问题:

锁定delete 语句:

m.Lock()
delete(m.Data, i)
m.Unlock()

或者,将Round() 方法中的两个案例提取为两个方法,覆盖通道:

func (m *Map) Round() {
  for i, v := range m.Data {
    fmt.Println("-----", v)
    delete(m.Data, i)
  }
}

func (m *Map) Done() {
  for range done {
    println("bye")
    break
  }
}

func main() {
  // ...
  go Round()
  go Done()
}

【讨论】:

  • 谢谢,这解决了两个数据竞争。我更新了它。但是第二次数据竞赛仍然发生
  • (根据您对我对该问题的评论的回复,注意到第二场比赛似乎也已解决。)
猜你喜欢
  • 2018-03-26
  • 2020-05-07
  • 1970-01-01
  • 2017-09-09
  • 2018-04-02
  • 2021-08-21
  • 1970-01-01
  • 1970-01-01
  • 2021-10-29
相关资源
最近更新 更多