golang 解读(2) sync.Map

sync.map就是1.9版本带的线程安全map.

[[在Go 1.6之前, 内置的map类型是部分goroutine安全的,并发的读没有问题,并发的写可能有问题。自go 1.6之后, 并发地读写map会报错,

所以go 1.9之前的解决方案是额外绑定一个锁,封装成一个新的struct或者单独使用锁都可以。

在Go官方blog的Go maps in action一文中,提供了一种简便的解决方案。

var counter = struct{
    sync.RWMutex  //读写锁
    m map[string]int
}{m: make(map[string]int)}

它使用嵌入struct为map增加一个读写锁。

读数据的时候很方便的加锁:

counter.RLock() // 读锁定
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)

写数据的时候:

unter.Lock()// 写锁定
counter.m["some_key"]++
counter.Unlock()

golang中sync包实现了两种锁

Mutex (互斥锁:适用于读写不确定场景,即读写次数没有明显的区别,并且只允许只有一个读或者写的场景,所以该锁叶叫做全局锁)

和RWMutex(读写锁:该锁可以加多个读锁或者一个写锁,其经常用于读次数远远多于写次数的场景;如果在添加写锁之前已经有其他的读锁和写锁,则lock就会阻塞直到该锁可用,为确保该锁最终可用,已阻塞的 Lock 调用会从获得的锁中排除新的读取器,即写锁权限高于读锁,有写锁时优先进行写锁定

区别参见:http://blog.csdn.net/chenbaoke/article/details/41957725

]]

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

用法:

Store(key,value interface{})

说明: 存储一个设置的键值

LoadOrStore(key,value interface{}) (actual interface{}, loaded bool)

说明: 返回键的现有值(如果存在),否则存储并返回给定的值,如果是读取则返回true,如果是存储返回false

Load(keyinterface{}) (value interface{}, ok bool)

说明: 读取存储在map中的值,如果没有值,则返回nilOK的结果表示是否在map中找到值

Delete(keyinterface{})

说明: 删除键对应的值

Range(ffunc(key, value interface{}) bool)

说明: 循环读取map中的值

sync.Map的实现有几个优化点,

空间换时间。 通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。 使用只读数据(read),避免读写冲突。 动态调整,miss次数多了之后,将dirty数据提升为read。 double-checking。 延迟删除。 删除一个键值只是打标记,只有在提升dirty的时候才清理删除的数据。 优先从read读取、更新、删除,因为对read的读取不需要锁。
参考:https://studygolang.com/articles/10511

sync.Map的数据结构,它使用了冗余的数据结构read、dirty。dirty中会包含read中为删除的entries,新增加的entries会加入到dirty中。

type Map struct {
    // 当涉及到dirty数据的操作的时候,需要使用这个锁
    mu Mutex
    // 一个只读的数据结构,因为只读,所以不会有读写冲突。从这个数据中读取总是安全的。
    // 实际上,也会更新这个数据的entries,如果entry是未删除的(unexpunged), 并不需要加锁。如果entry已经被删除了,需要加锁,以便更新dirty数据。
    read atomic.Value // readOnly
    // dirty数据包含当前的map包含的entries,它包含最新的entries(包括read中未删除的数据,虽有冗余,但是提升dirty字段为read的时候非常快,不用一个一个的复制,而是直接将这个数据结构作为read字段的一部分),
    // 有些数据还可能没有移动到read字段中。对于dirty的操作需要加锁,因为对它的操作可能会有读写竞争。
    // 当dirty为空的时候, 比如初始化或者刚提升完,下一次的写操作会复制read字段中未删除的数据到这个数据中。
    dirty map[interface{}]*entry
    // 当从Map中读取entry的时候,如果read中不包含这个entry,会尝试从dirty中读取,这个时候会将misses加一,
    // 当misses累积到 dirty的长度的时候, 就会将dirty提升为read,避免从dirty中miss太多次。因为操作dirty需要加锁。
    misses int
}

read的数据结构是:

type readOnly struct {
    m       map[interface{}]*entry
    amended bool // 如果Map.dirty有些数据不在中的时候,这个值为true
}

amended指明Map.dirty中有readOnly.m未包含的数据,所以如果从Map.read找不到数据的话,还要进一步到Map.dirty中查找。

使用列子:

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    // 1.首先从m.read中得到只读readOnly,从它的map中查找,不需要加锁
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    // 2. 如果没找到,并且m.dirty中有新数据,需要从m.dirty查找,这个时候需要加锁
    if !ok && read.amended {
        m.mu.Lock()
        // 双检查,避免加锁的时候m.dirty提升为m.read,这个时候m.read可能被替换了。
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        // 如果m.read中还是不存在,并且m.dirty中有新数据
        if !ok && read.amended {
            // 从m.dirty查找
            e, ok = m.dirty[key]
            // 不管m.dirty中存不存在,都将misses计数加一
            // missLocked()中满足条件后就会提升m.dirty
            m.missLocked()
        }
        m.mu.Unlock()
    }
    if !ok {
        return nil, false
    }
    return e.load()
}
func (m *Map) Store(key, value interface{}) {
    // 如果m.read存在这个键,并且这个entry没有被标记删除,尝试直接存储。
    // 因为m.dirty也指向这个entry,所以m.dirty也保持最新的entry。
    read, _ := m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok && e.tryStore(&value) {
        return
    }
    // 如果`m.read`不存在或者已经被标记删除
    m.mu.Lock()
    read, _ = m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok {
        if e.unexpungeLocked() { //标记成未被删除
            m.dirty[key] = e //m.dirty中不存在这个键,所以加入m.dirty
        }
        e.storeLocked(&value) //更新
    } else if e, ok := m.dirty[key]; ok { // m.dirty存在这个键,更新
        e.storeLocked(&value)
    } else { //新键值
        if !read.amended { //m.dirty中没有新的数据,往m.dirty中增加第一个新键
            m.dirtyLocked() //从m.read中复制未删除的数据
            m.read.Store(readOnly{m: read.m, amended: true})
        }
        m.dirty[key] = newEntry(value) //将这个entry加入到m.dirty中
    }
    m.mu.Unlock()
}
func (m *Map) dirtyLocked() {
    if m.dirty != nil {
        return
    }
    read, _ := m.read.Load().(readOnly)
    m.dirty = make(map[interface{}]*entry, len(read.m))
    for k, e := range read.m {
        if !e.tryExpungeLocked() {
            m.dirty[k] = e
        }
    }
}
func (e *entry) tryExpungeLocked() (isExpunged bool) {
    p := atomic.LoadPointer(&e.p)
    for p == nil {
        // 将已经删除标记为nil的数据标记为expunged
        if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
            return true
        }
        p = atomic.LoadPointer(&e.p)
    }
    return p == expunged
}
以上操作都是先从操作m.read开始的,不满足条件再加锁,然后操作m.dirty。

Store可能会在某种情况下(初始化或者m.dirty刚被提升后)从m.read中复制数据,如果这个时候m.read中数据量非常大,可能会影响性能。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
sync.Map 是 Go 语言标准库中提供的一种并发安全的字典类型,它可以被多个 goroutine 安全地访问和修改。在多个 goroutine 中并发地读写一个 map 时,会出现竞争条件,从而导致数据不一致。而 sync.Map 利用了一些锁的技巧,避免了这种竞争条件的发生,从而实现了高效的并发安全访问。 sync.Map 的 API 非常简单,主要包括以下几个方法: 1. Store(key, value interface{}):将一个键值对存储到 sync.Map 中。 2. Load(key interface{}) (value interface{}, ok bool):根据键从 sync.Map 中获取对应的值。 3. LoadOrStore(key, value interface{}) (actual interface{}, loaded bool):如果键存在于 sync.Map 中,则返回对应的值和 true,否则将键值对存储到 sync.Map 中并返回新的值和 false。 4. Delete(key interface{}):从 sync.Map 中删除一个键值对。 5. Range(f func(key, value interface{}) bool):遍历 sync.Map 中的键值对,并对每一个键值对调用函数 f,如果 f 返回 false,则停止遍历。 下面是一个使用 sync.Map 的简单例子,展示了如何在多个 goroutine 中并发地访问和修改 sync.Map: ``` package main import ( "fmt" "sync" ) func main() { var m sync.Map var wg sync.WaitGroup wg.Add(2) // goroutine 1: 向 sync.Map 中存储键值对 go func() { defer wg.Done() m.Store("key1", "value1") m.Store("key2", "value2") }() // goroutine 2: 从 sync.Map 中加载键值对 go func() { defer wg.Done() if v, ok := m.Load("key1"); ok { fmt.Println("value for key1:", v) } if v, ok := m.Load("key2"); ok { fmt.Println("value for key2:", v) } }() wg.Wait() } ``` 在上面的例子中,我们首先创建了一个 sync.Map 对象 m。然后在两个 goroutine 中同时访问这个对象,一个 goroutine 向其中存储键值对,另一个 goroutine 则从其中加载键值对。由于 sync.Map 是并发安全的,所以这两个 goroutine 可以并发地访问和修改 sync.Map,而不会出现竞争条件。 需要注意的是,虽然 sync.Map 是并发安全的,但它并不是用来替代普通的 map 的。如果你只是需要在某个 goroutine 中访问和修改一个 map,那么你应该使用普通的 map,因为 sync.Map 的性能会比较差。只有在需要多个 goroutine 并发地访问和修改一个 map 时,才应该考虑使用 sync.Map

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值