Go语言syncMap实现的功能和使用场景

Go语言syncMap实现的功能和使用场景

在go语言提供的标准map类型中,如果涉及到协程读取map的场景,会存在并发读写的问题,并发操作map的会导致数据不一致,

 为什么map不是并发安全的?
 因为map的读写操作不是原子操作,当多个协程同时读写map时,会发生脏读和脏写导致数据不一致的问题。

在go 语言1.9版本后引入了syncMap,可以提供更加便捷的并发map操作方法, 例如:

 var m sync.Map

可以使用syncMap的方法来进行并发安全的map操作,例如:

 m.Store(key, value)
 m.Load(key)
 m.Delete(key)

这些方法都是并发安全的,不会导致数据不一致的问题。

但是该数据类型仅适合一定程度上的读多写少场景,syncMap内部使用了读写锁来实现并发安全,当写操作比较多时,会导致读操作的阻塞,影响性能。 源码:https://github.com/golang/go/blob/go1.23.0/src/sync/map.go

func (m *Map) Swap(key, value any) (previous any, loaded bool) {
    read := m.loadReadOnly()
    if e, ok := read.m[key]; ok {
        if v, ok := e.trySwap(&value); ok {
            if v == nil {
                return nil, false
            }
            return *v, true
        }
    }

    m.mu.Lock()
    read = m.loadReadOnly()
    if e, ok := read.m[key]; ok {
        if e.unexpungeLocked() {
            // The entry was previously expunged, which implies that there is a
            // non-nil dirty map and this entry is not in it.
            m.dirty[key] = e
        }
        if v := e.swapLocked(&value); v != nil {
            loaded = true
            previous = *v
        }
    } else if e, ok := m.dirty[key]; ok {
        if v := e.swapLocked(&value); v != nil {
            loaded = true
            previous = *v
        }
    } else {
        if !read.amended {
            // We're adding the first new key to the dirty map.
            // Make sure it is allocated and mark the read-only map as incomplete.
            m.dirtyLocked()
            m.read.Store(&readOnly{m: read.m, amended: true})
        }
        m.dirty[key] = newEntry(value)
    }
    m.mu.Unlock()
    return previous, loaded
}

总结

  • 如果遇到并发写入更多更频繁的场景,需要考虑读写锁频繁阻塞,性能会大幅下降,
  • 应该考虑使用其他的方式实现该需求,可以考虑使用redis hash或者将并发操作的场景转换为在异步task进程中进行处理。

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top