线程安全的引用计数资源映射

huangapple go评论88阅读模式
英文:

Thread-safe map of ref-counted resources

问题

管理一组资源的方法如下:

  1. 通过全局列表(例如哈希映射)按名称访问资源。
  2. 多个线程同时访问这些资源。
  3. 使用引用计数(Golang缺乏"弱引用";请参阅https://groups.google.com/forum/#!topic/golang-nuts/PYWxjT2v6ps)。

示例:

var theList tMap // 全局变量

// 在线程A、B、C等中
  aThing := theList.ref("aThing") // 如果存在,则引用计数加1;否则插入
  // 使用aThing
  theList.unref("aThing") // 引用计数减1;如果为0,则移除

编辑:我原本期望能找到一种推荐的模式,但没有找到。因此,我想出了以下解决方案:

type tMap struct {
   set map[string]*tMapSet
   updt sync.RWMutex
}

type tMapSet struct {
   ...
   refcount int32
}

func (o *tMap) ref(iId string) *tMapSet {
   o.updt.RLock()
   aSet := o.set[iId]
   if aSet != nil {
      atomic.AddInt32(&aSet.refcount, 1)
   }
   o.updt.RUnlock()

   if aSet == nil {
      o.updt.Lock()
      if aTemp := o.set[iId]; aTemp != nil {
         aSet = aTemp
         aSet.refcount++
      } else {
         aSet = &tMapSet{refcount:1}
         o.set[iId] = aSet
      }
      o.updt.Unlock()
   }
   return aSet
}

func (o *tMap) unref(iId string) {
   o.updt.RLock()
   aSet := o.set[iId]
   aN := atomic.AddInt32(&aSet.refcount, -1) // 如果找不到set[iId],则会出错
   o.updt.RUnlock()

   if aN == 0 {
      o.updt.Lock()
      if aSet.refcount == 0 {
         delete(o.set, iId)
      }
      o.updt.Unlock()
   }
}

对上述代码的清晰度或简洁度有什么改进建议吗?

英文:

On managing a collection of resources which are:

  1. accessible by name through a global list (e.g. a hash-map)
  2. accessed concurrently from multiple threads
  3. ref-counted (Golang lacks "weak references"; see
    https://groups.google.com/forum/#!topic/golang-nuts/PYWxjT2v6ps)

Example:

var theList tMap // global

// in thread A, B, C etc
  aThing := theList.ref("aThing") // if exists then refcount++, else insert
  // use aThing
  theList.unref("aThing") // refcount--, if 0 then remove

Edit: I expected to find a recommended pattern for this, but nothing turned up. So I came up with this:

type tMap struct {
   set map[string]*tMapSet
   updt sync.RWMutex
}

type tMapSet struct {
   ...
   refcount int32
}

func (o *tMap) ref(iId string) *tMapSet {
   o.updt.RLock()
   aSet := o.set[iId]
   if aSet != nil {
      atomic.AddInt32(&aSet.refcount, 1)
   }
   o.updt.RUnlock()

   if aSet == nil {
      o.updt.Lock()
      if aTemp := o.set[iId]; aTemp != nil {
         aSet = aTemp
         aSet.refcount++
      } else {
         aSet = &tMapSet{refcount:1}
         o.set[iId] = aSet
      }
      o.updt.Unlock()
   }
   return aSet
}

func (o *tMap) unref(iId string) {
   o.updt.RLock()
   aSet := o.set[iId]
   aN := atomic.AddInt32(&aSet.refcount, -1) // crash if set[iId] not found
   o.updt.RUnlock()

   if aN == 0 {
      o.updt.Lock()
      if aSet.refcount == 0 {
         delete(o.set, iId)
      }
      o.updt.Unlock()
   }
}

Suggestions improving the clarity or brevity of the above?

答案1

得分: 1

只需在访问时将您的映射包装在互斥锁中以保护访问(如果读取操作较多且写入操作较少,您也可以使用RWMutex,可能数据应该存储具体类型)。以下是一个示例,具有几个方法:

type MagicMap struct {
    sync.Mutex
    data   map[string]interface{}
    counts map[string]int
}

func (m *MagicMap) Get(key string) interface{} {
    m.Lock()
    defer m.Unlock()
    return m.data[key]
}

func (m *MagicMap) Add(key string, value interface{}) {
    m.Lock()
    m.data[key] = value
    m.counts[key] = m.counts[key] + 1
    m.Unlock()
}

func (m *MagicMap) Remove(key string) {
    m.Lock()
    count := m.counts[key]
    count -= 1
    if count < 1 {
        delete(m.data, key)
        delete(m.counts, key)
    } else {
        m.counts[key] = count
    }
    m.Unlock()
}

这段代码未经测试,写得很快,可能存在错误,但希望能给您提供一个尝试的方向。如果您愿意,您也可以只使用一个map[mystruct]int来存储结构体和计数。示例中使用了分开的键和值。

这里是另一个使用互斥锁保护映射访问的示例:https://gobyexample.com/mutexes

您还可以在Go 1.9中使用新的sync.Map,但它比仅使用互斥锁要慢。

英文:

Just wrap your map in a mutex to protect access (you could also use RWMutex if lots of reads and few writes, probably data should store a concrete type too). Something like this with a few methods would be fine:

type MagicMap struct {
	sync.Mutex
	data   map[string]interface{}
	counts map[string]int
}

func (m MagicMap) Get(key string) interface{} {
  m.Lock()
  defer m.Unlock()
  return m.data[key]
}

func (m MagicMap) Add(key string, value interface{}) {
	m.Lock()
	m.data[key] = value
	m.counts[key] = m.counts[key] + 1
	m.Unlock()
}

func (m MagicMap) Remove(key string) {
	m.Lock()
	count := m.counts[key]
	count -= 1
	if count &lt; 1 {
	  delete(m.data, key)
	  delete(m.counts, key)
	} else {
	  m.counts[key] = count
	}
	m.Unlock()
}

This is untested, written quickly, and may be buggy, but hopefully gives you a direction to try out. If you want you could just have one map with map[mystruct]int to store structs and counts. The example has separate keys and values.

https://play.golang.org/p/9k1lNRpqua

Here's another example of using a mutex to protect map access:

https://gobyexample.com/mutexes

You can also use the new sync.Map in Go 1.9 but it's slower than just using a mutex.

huangapple
  • 本文由 发表于 2017年8月11日 06:04:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/45624115.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定