douque2016
2017-11-22 11:29
浏览 28
已采纳

初始化地图元素,其中value是具有互斥锁golang的结构

I have a map where every value is a pointer to another struct that itself has a lock.

type StatMap map[string]*Stats

type Stats struct {
    sync.RWMutex
    someStats, someMoreStats float64
}

I have implemented a method where I pack the StatMap into another struct and have a Mutex lock for the entire map, but I am expecting to modify every entry in the map simoultaniously from hundreds of goroutines, so it would be more effective to lock every element in the map so that two or more goroutines can read and modify values for entries in parallell.

What I am wondering is how I can initialize a new entry in the map whenever there comes a new key? I cannot lock the entry if it isn't in the map already, and I cannot check if it is in the map (as far as I know) in case another goroutine is currently modifying that entry.

I do not know what keys will be in the map before runtime.

My current implementation (that causes data races):

initializeStatMap("key")
statMap["key"].Lock()
// . . . 


func initializeStatMap(key string) {
    if statMap[key] != nil {
        return
    }
    statMap[key] = &Stats{someStats: 0, someMoreStats: 0}
}
  • 写回答
  • 好问题 提建议
  • 追加酬金
  • 关注问题
  • 邀请回答

1条回答 默认 最新

  • douhuan1937 2017-11-22 12:55
    最佳回答

    The Go's map semantics are as follows:

    • A map stores values (not variables) and that's why these values are not adressable, and that's why you can't do something like

      type T struct {
          X int
      }
      m := make(map[int]T)
      m[0] = T{}
      m[0].x = 42 // won't compile
      
    • This requirement mostly comes from from the fact a map, being an intricate dynamic data structure, should allow its particular implementations to physically move the values it contains around in memory — when doing rebalancing etc.

    That's why the only three operations a map supports is adding (or replacing) of its elements, getting them back and deleting them.

    A map is not safe for concurrent use, so in order to do any of those three operations on the same map concurrently, you need to protect it in one way or another.

    Consequently, once you have read a value from a map, orchestrating concurrent access to it is a completely another story, and here we're facing another fact about the map's semantics: since it keeps values and is free to copy them around in memory, it's not allowed to keep in a map anything which you want to have reference semantics. For instance, it would be incorrect to keep values of your Stats type in the map directly—because they embed instances of sync.Mutex, and copying of them prohibited after they are first used. Here you're already doing the right thing by storing pointers to your variables.

    Now you can see that it's pretty OK to roll like this:

    1. Access the map itself to get a value bound to a key in a concurrent-safe way (say, by holding a lock).
    2. Lock the mutex on that variable and operate on it. That does not involve the map at all.

    The only remaining possible problem is as follows. Suppose you're protecting the access to your map with a lock. So you grab the lock, obtain the value bound to a key, by copying it to a variable, release the lock and work with the copy of the value.

    Now while you're working with the copy of that value another goroutine is free to update the map by deleting the value or replacing it.

    While in your case it's fine technically — because your map operates on pointers to variables, and it's fine to copy pointers — this might be inappropriate from the standpoint of the semantics of your program, and this is something you have to think through. To make it more clear, once you've got a pointer to some instance of Stats and locked it, a pointer to this instance can be removed from the map, or the map entry which held it could be updated by another pointer — pointing to another instance of Stats, so once you're done with the instance, it might have become unreachable via the map.

    评论
    解决 无用
    打赏 举报

相关推荐 更多相似问题