地址:88250.b3log.org/optimizing-…
Catena (时序存储引擎)
中有一个函数的实现备受争议,它从 map 中根据指定的 name 获取一个 metricSource。每一次插入操作都会至少调用一次这个函数,现实场景中该函数调用更是频繁,并且是跨多个协程的,因此我们必须要考虑同步。
该函数从 map[string]*metricSource
中根据指定的 name 获取一个指向 metricSource 的指针,如果获取不到则创建一个并返回。其中要注意的关键点是我们只会对这个 map 进行插入操作。
- var source *memorySource
- var present bool
-
- p.lock.Lock() // lock the mutex
- defer p.lock.Unlock() // unlock the mutex at the end
-
- if source, present = p.sources[name]; !present {
- // The source wasn't found, so we'll create it.
- source = &memorySource{
- name: name,
- metrics: map[string]*memoryMetric{},
- }
-
- // Insert the newly created *memorySource.
- p.sources[name] = source
- }
- 复制代码
经测试,该实现大约可以达到 1,400,000 插入/秒(通过协程并发调用,GOMAXPROCS 设置为 4)。看上去很快,但实际上它是慢于单个协程的,因为多个协程间存在锁竞争。
下面给出不存在竟态条件、线程安全,应该算是“正确”的版本了。使用了 RWMutex,读操作不会被锁,写操作保持同步。
- var source *memorySource
- var present bool
-
- p.lock.RLock()
- if source, present = p.sources[name]; !present {
- // The source wasn't found, so we'll create it.
- p.lock.RUnlock()
- p.lock.Lock()
- if source, present = p.sources[name]; !present {
- source = &memorySource{
- name: name,
- metrics: map[string]*memoryMetric{},
- }
-
- // Insert the newly created *memorySource.
- p.sources[name] = source
- }
- p.lock.Unlock()
- } else {
- p.lock.RUnlock()
- }
- 复制代码