2016-06-10 59 views
3

我有以下代碼,並且我正在進行數據競賽。 Round函數定期檢查運行功能刪除地圖 的內容,當我讀到這裏:Is it safe to remove selected keys from Golang map within a range loop?Golang數據競賽,66退出狀態

從地圖上是安全的刪除數據,但我有數據爭

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 

但是,當我將地圖的值類型更改爲int或字符串時,沒有數據競爭。

你推薦什麼解決方案?

+0

我認爲這是影響結果的類型的巧合(但也許有人會更好地知道會回覆)。你需要鎖定地圖迭代,並在'Round()'中刪除。 – twotwotwo

+0

謝謝,所以我用RLock和RUnlock封裝了範圍循環,這樣刪除了第二個數據競賽 – maksadbek

+1

比賽檢測器告訴你_exactly_比賽發生在哪裏。你在沒有鎖的情況下調用'delete'。 – JimB

回答

1

更新:

但是,當我改變地圖爲int或字符串的值類型,沒有數據的比賽。

我測試了你的代碼。將映射的值類型更改爲int或字符串將繼續產生競爭條件。嘗試在while循環在你的shell中運行它,你會明白我的意思:

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

不應該有值類型之間的差異。


正如賽跑探測器報道的那樣,有兩種不同的競賽條件。第一場比賽(你已經解決)發生在第54行的讀取(i)和第51行的寫入(到i)之間。這是因爲你的goroutine closure持有對i的引用,其改變了for循環在您的main goroutine中。您可以通過以下方法解決,要麼擺脫println(">>", 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() 
} 
+0

謝謝,這解決了兩個數據競賽。我更新了它。但第二場數據競賽仍然發生在 – maksadbek

+0

(注意到第二場比賽似乎也得到了解決,根據你對這個問題的評論的回覆。) – twotwotwo