【发布时间】:2016-12-23 17:02:36
【问题描述】:
我正在编写一个简单的缓存机制,它有一个 Add 一个 Evict 和一个 Search 方法。 Search 目前尚未实现,因此无需担心。
有相当多的 goroutine 调用 Add 来添加数据,并且只有一个在 evict 循环中运行以驱逐数据。一旦我在它上面放置了一些严重的流量,Go就会抛出说在地图metricCache上有一个并发的读写访问,但我看不出这是怎么发生的,因为它周围有锁。我正在使用 Go 1.7。
文件mdata/cache.go:
57: func NewCCache() *CCache {
58: cc := &CCache{
59: lock: sync.RWMutex{},
60: metricCache: make(map[string]*CCacheMetric),
61: accnt: accnt.NewFlatAccnt(maxSize),
62: }
63: go cc.evictLoop()
64: return cc
65: }
66:
67: func (c *CCache) evictLoop() {
68: evictQ := c.accnt.GetEvictQ()
69: for target := range evictQ {
70: c.evict(target)
71: }
72: }
73:
74: func (c *CCache) Add(metric string, prev uint32, itergen chunk.IterGen) {
75: c.lock.Lock()
76:
77: if ccm, ok := c.metricCache[metric]; !ok {
78: var ccm *CCacheMetric
79: ccm = NewCCacheMetric()
80: ccm.Init(prev, itergen)
81: c.metricCache[metric] = ccm
82: } else {
83: ccm.Add(prev, itergen)
84: }
85: c.lock.Unlock()
86:
87: c.accnt.AddChunk(metric, itergen.Ts(), itergen.Size())
88: }
89:
90: func (c *CCache) evict(target *accnt.EvictTarget) {
91: c.lock.Lock()
92:
93: if _, ok := c.metricCache[target.Metric]; ok {
94: log.Debug("cache: evicting chunk %d on metric %s\n", target.Ts, target.Metric)
95: length := c.metricCache[target.Metric].Del(target.Ts)
96: if length == 0 {
97: delete(c.metricCache, target.Metric)
98: }
99: }
100:
101: c.lock.Unlock()
102: }
这是错误信息:
metrictank_1 | fatal error: concurrent map read and map write
metrictank_1 |
metrictank_1 | goroutine 3159 [running]:
metrictank_1 | runtime.throw(0xaade7e, 0x21)
metrictank_1 | /usr/local/go/src/runtime/panic.go:566 +0x95 fp=0xc4216a7eb8 sp=0xc4216a7e98
metrictank_1 | runtime.mapaccess2_faststr(0x9e22c0, 0xc42031e600, 0xc4210c2b10, 0x22, 0x28, 0xa585d5496)
metrictank_1 | /usr/local/go/src/runtime/hashmap_fast.go:306 +0x52b fp=0xc4216a7f18 sp=0xc4216a7eb8
metrictank_1 | github.com/raintank/metrictank/mdata/cache.(*CCache).Add(0xc4202fa070, 0xc4210c2b10, 0x22, 0x0, 0xc421875f82, 0x25, 0x25, 0xa585d5496)
metrictank_1 | /home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:77 +0x63 fp=0xc4216a7f80 sp=0xc4216a7f18
metrictank_1 | runtime.goexit()
metrictank_1 | /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1 fp=0xc4216a7f88 sp=0xc4216a7f80
metrictank_1 | created by github.com/raintank/metrictank/api.(*Server).getSeries
metrictank_1 | /home/mst/go/src/github.com/raintank/metrictank/api/dataprocessor.go:442 +0x122b
更新:我用-race 重新编译,现在我得到一个不同的错误。这看起来好像RWMutex 完全无效,因为根据回溯,问题一定出在evict 和Add 方法的组合中。
==================
WARNING: DATA RACE
Read at 0x00c4201c81e0 by goroutine 215:
runtime.mapaccess2_faststr()
/usr/local/go/src/runtime/hashmap_fast.go:297 +0x0
github.com/raintank/metrictank/mdata/cache.(*CCache).Add()
/home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:77 +0xaa
Previous write at 0x00c4201c81e0 by goroutine 155:
runtime.mapdelete()
/usr/local/go/src/runtime/hashmap.go:558 +0x0
github.com/raintank/metrictank/mdata/cache.(*CCache).evict()
/home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:97 +0x30e
github.com/raintank/metrictank/mdata/cache.(*CCache).evictLoop()
/home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:70 +0xb3
Goroutine 215 (running) created at:
github.com/raintank/metrictank/api.(*Server).getSeries()
/home/mst/go/src/github.com/raintank/metrictank/api/dataprocessor.go:442 +0x17c9
github.com/raintank/metrictank/api.(*Server).getTarget()
/home/mst/go/src/github.com/raintank/metrictank/api/dataprocessor.go:331 +0x9c3
github.com/raintank/metrictank/api.(*Server).getTargetsLocal.func1()
/home/mst/go/src/github.com/raintank/metrictank/api/dataprocessor.go:284 +0xa9
Goroutine 155 (running) created at:
github.com/raintank/metrictank/mdata/cache.NewCCache()
/home/mst/go/src/github.com/raintank/metrictank/mdata/cache/cache.go:63 +0x12f
main.main()
/home/mst/go/src/github.com/raintank/metrictank/metrictank.go:388 +0x246c
==================
【问题讨论】:
-
请提供竞争检测器显示读写访问权限的确切位置。
-
是的,你是用
-race标志构建的,对吧?有了这个,Go 会告诉你问题出在哪里。如果没有它,您可能会遇到某种最坏情况的损坏内存检测错误。 -
从这段代码中我没有看到任何明显的问题。除了这不可能是所有代码,或者你为什么只使用一个 RWLock 作为一个简单的互斥锁?此代码从不读取锁定它。我怀疑 goroutine 或其他结构已经并且正在使用指向您的地图的指针。
-
看起来确实是锁的问题。可能导致这种情况的一件事是,如果您曾经在任何地方制作了该sync.RWMutex 的副本。永远不要那样做。
-
您可能想尝试将
lock声明为指向可以解决锁复制问题的RWMutex 的指针。
标签: go synchronization