美文网首页
Go语言高并发Map解决方案

Go语言高并发Map解决方案

作者: 突击手平头哥 | 来源:发表于2021-07-11 22:57 被阅读0次

Go语言高并发Map解决方案

Go语言基础库中的map不是并发安全的,不过基于读写锁可以实现线程安全;不过在Go1.9版本时,官方提供了sync.Map的线程安全Map

读写锁实现安全map

package main

import (
    "fmt"
    "sync"
)

type SafeMap struct {
    data map[int]string
    mutex sync.Mutex
}

func (m *SafeMap) Get(key int) string {
    m.mutex.Lock()
    defer m.mutex.Unlock()
    if v, ok := m.data[key]; ok {
        return v
    }
    return ""
}

func (m *SafeMap) Put(key int, value string) {
    m.mutex.Lock()
    defer m.mutex.Unlock()
    m.data[key] = value
}


func  main() {

    m := SafeMap{
        data : make(map[int]string),
    }

    m.Put(1, "1")
    fmt.Println(m.Get(1))
}

这种实现的map适用于读多写少的情况,一般场景下足以满足

sync.Map

示例代码

package main

import (
    "fmt"
    "sync"
)



func  main() {

    m := sync.Map{}
    m.Store(1, "1")
    fmt.Println(m.Load(1))
}

sync.Map的借口和普通的Map是不一样的

sync.Map的原理

读写分离

type readOnly struct {
    m       map[interface{}]*entry
    amended bool                    // 如果dirty和read数据不一致则此字段为true
}
type Map struct {
    mu Mutex
    read atomic.Value // readOnly
    dirty map[interface{}]*entry
    misses int
}
  • 写操作直接写入dirty中去
  • 读操作则是优先从read中读取,如果读取不到再到dirty中读取;对read的读取操作时并发安全的,修改等操作也全部是原子性操作
  • 在查询穿透达到一定数量超过dirty的长度时用dirty的数据替代read

添加操作

func (m *Map) Store(key, value interface{}) {
    read, _ := m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok && e.tryStore(&value) {
        return
    }

    m.mu.Lock()
    read, _ = m.read.Load().(readOnly)
    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
        }
        e.storeLocked(&value)
    } else if e, ok := m.dirty[key]; ok {
        e.storeLocked(&value)
    } 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()
}
  • 如果数据在read中没有,则仅在dirty中添加、修改数据
  • 如果数据在read中有,那么readdirty的数据一起修改

删除操作

读写分离的操作可以解决写入、读取的冲突问题,以上的机制是处理不了删除的

func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    if !ok && read.amended {
        m.mu.Lock()
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        if !ok && read.amended {
            e, ok = m.dirty[key]
            delete(m.dirty, key)
            // Regardless of whether the entry was present, record a miss: this key
            // will take the slow path until the dirty map is promoted to the read
            // map.
            m.missLocked()
        }
        m.mu.Unlock()
    }
    if ok {
        return e.delete()
    }
    return nil, false
}
func (e *entry) delete() (value interface{}, ok bool) {
    for {
        p := atomic.LoadPointer(&e.p)
        if p == nil || p == expunged {
            return nil, false
        }
        if atomic.CompareAndSwapPointer(&e.p, p, nil) {
            return *(*interface{})(p), true
        }
    }
}

  在进行删除操作时并未直接从map中删除而是通过标记的方式删除,即将read值设置为nil来实现

//统计查询失败次数
func (m *Map) missLocked() {
    m.misses++
    if m.misses < len(m.dirty) {
        return
    }
    m.read.Store(readOnly{m: m.dirty})
    m.dirty = nil
    m.misses = 0
}

//存储数据时重新生成dirty
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
        }
    }
}

在没次查询次数超过限制时就会将dirty提升为read,但是却把dirty设置为nil

在存储、修改数据时会再次从read中复制一份数据为dirty这个时候才是真正的将数据删除掉了

总结

  对于sync.Map的代码细节就不过多分析了,内部有很多双重功能判断需要结合业务来分析;这个读写锁更加的高级,但是实际上面对写并发较多的情况下依然存在不足。

分段锁Map

并发Map更常见的解决方案是:分段锁

分段锁Map的实现思路

  创建多个Map,对于读写操作的key先进行一次HASH操作,将对此key的操作放到都放到一个Map上,这样读写操作的锁只需要针对这一个Map来做,不同Map之间互不影响。

示例代码

package concurrentmap

import "sync"

type shard struct {
    data map[string]interface{}
    mutex   sync.Mutex
}

type Map struct {
    table []*shard
    count uint32
}

//可以额外实现指定count和hash函数的接口
func NewMap() *Map {
    m := Map {}
    var count uint32 = 32
    for i := uint32(0); i < count; i++ {
        m.table = append(m.table, &shard{
            data : make(map[string]interface{}),
        })
    }
    m.count = count
    return &m
}


const prime32 = uint32(16777619)
func fnv32(key string) uint32 {
    hash := uint32(2166136261)
    for i := 0; i < len(key); i++ {
        hash *= prime32
        hash ^= uint32(key[i])
    }
    return hash
}

func (m *Map) getShared(u uint32) *shard {
    return m.table[int(u % m.count)]
}

func (m *Map) Put(key string, value interface{}) {
    shard := m.getShared(fnv32(key))
    shard.mutex.Lock()
    defer shard.mutex.Unlock()
    shard.data[key] = value
}

func (m *Map) Get(key string) (interface{}, bool) {
    shard := m.getShared(fnv32(key))
    shard.mutex.Lock()
    if v, ok := shard.data[key]; ok {
        return v, true
    }
    return nil, false
}

相关文章

  • Go语言高并发Map解决方案

    Go语言高并发Map解决方案 Go语言基础库中的map不是并发安全的,不过基于读写锁可以实现线程安全;不过在Go1...

  • GO语言初级学习之代码案例13 (QQ群聊)

    @(go语言 黑马)[GO语言] 并发聊天室 题目:利用Go语言高并发的特性,编写一个类似QQ群聊功能的并发聊天服...

  • Go 并发编程-goroutine 初体验

    说到 Go 语言,被人讨论最多的就是 Go 很擅长做高并发,并且不需要依赖外部的库,语言本身就支持高并发。 Go ...

  • map与sync.Map

    Go 语言原生 map 并不是线程安全的,对它进行并发读写操作的时候,需要加锁。而 sync.map 则是一种并发...

  • Go语言——sync.Map详解

    Go语言——sync.Map详解 sync.Map是1.9才推荐的并发安全的map,除了互斥量以外,还运用了原子操...

  • Go语言并发

    Go语言并发 Go语言级别支持协程,叫做goroutine Go 语言从语言层面支持并发和并行的开发操作 Go并发...

  • Go基础语法(九)

    Go语言并发 Go 是并发式语言,而不是并行式语言。 并发是指立即处理多个任务的能力。 Go 编程语言原生支持并发...

  • 面试问题总结(一)Golang

    使用go语言的好处: go语言的设计是务实的, go在针对并发上进行了优化, 并且支持大规模高并发, 又由于单一的...

  • Goroutine 是如何运行的

    在 Go 语言中,没有线程,只有 goroutine,这也是 Go 语言原生支持高并发的关键。 goroutine...

  • Go Map 为什么是非线程安全的?

    Go map 默认是并发不安全的,同时对 map 进行并发读写的时,程序会 panic,原因如下:Go 官方经过长...

网友评论

      本文标题:Go语言高并发Map解决方案

      本文链接:https://www.haomeiwen.com/subject/pamlpltx.html