• 背景
go内置的map不是线程安全的,如何
无锁实现线程安全的map呢?
  • 非线程安全的map
package main

import (
	"fmt"
	"strconv"
	"sync"
)

var d = make(map[string]int)
var w sync.WaitGroup

func get(key string) int {
	return d[key]
}


func set(key string,value int) {
	d[key]=value
}



func main(){
	for i :=0;i<300;i++{
		w.Add(1)
		go func(n int) {
			key := strconv.Itoa(n)
			set(key,n)
			fmt.Printf("k=:%v,v:=%v\n",key,get(key))
			w.Done()
		}(i)
	}
	w.Wait()
}

当并发数量大时,程序会报错
  • 读写锁实现
package main

import (
	"fmt"
	"strconv"
	"sync"
)

var d2 = make(map[string]int)
var w2 sync.WaitGroup

var lo sync.RWMutex

func getValue(key string) int {
	lo.RLock()
	defer lo.RUnlock()
	return d2[key]
}


func setValue(key string,value int) {
	lo.Lock()
	defer lo.Unlock()
	d2[key]=value
}



func main(){
	for i :=0;i<3000;i++{
		w2.Add(1)
		go func(n int) {
			key := strconv.Itoa(n)
			setValue(key,n)
			fmt.Printf("k=:%v,v:=%v\n",key,getValue(key))
			w2.Done()
		}(i)
	}
	w2.Wait()
}
  • 字典分片
# 相对直接针对整个字典加读写锁,分片锁 可以
# 缩小控制的区域 提高性能

package main

import "sync"

var SHARD_COUNT = 32

// 分成SHARD_COUNT个分片的map
type ConcurrentMap []*ConcurrentMapShared

// 通过RWMutex保护的线程安全的分片,包含一个map
type ConcurrentMapShared struct {
	items        map[string]interface{}
	sync.RWMutex // Read Write mutex, guards access to internal map.
}

// 创建并发map
func New() ConcurrentMap {
	m := make(ConcurrentMap, SHARD_COUNT)
	for i := 0; i < SHARD_COUNT; i++ {
		m[i] = &ConcurrentMapShared{items: make(map[string]interface{})}
	}
	return m
}


// 根据key计算分片索引
func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared {
	return m[uint(fnv32(key))%uint(SHARD_COUNT)]
}


#读取时 
func (m ConcurrentMap) Set(key string, value interface{}) {
    // 根据key计算出对应的分片
    shard := m.GetShard(key)
    shard.Lock() //对这个分片加锁,执行业务操作
    shard.items[key] = value
    shard.Unlock()
}

func (m ConcurrentMap) Get(key string) (interface{}, bool) {
    // 根据key计算出对应的分片
    shard := m.GetShard(key)
    shard.RLock()
    // 从这个分片读取key的值
    val, ok := shard.items[key]
    shard.RUnlock()
    return val, ok
}
  • sync.map
# 使用sync.map实现


package main

import (
	"fmt"
	"strconv"
	"sync"
)

var syncMap sync.Map
func main(){
	wait := sync.WaitGroup{}
	for i:=0;i<3000;i++{
		wait.Add(1)
		go func(n int) {
			key :=strconv.Itoa(n)
			syncMap.Store(key,n)
			value,_ := syncMap.Load(key)
			fmt.Printf("k=:%v,v:=%v\n", key, value)
			wait.Done()
		}(i)

	}
	wait.Wait()
}
  • 并发map的使用要求
类型场景
Sync.map1.只会增长的缓存系统中,一个 key 只写入一次而被读很多次
2.多个 goroutine 为不相交的键集读、写和重写键值对
使用场景比较局限,不常用
读写锁实现的map并发要求不高 使用方便
分片机制实现的map常用,并发要求高

可以根据实际业务场景测试三种不同的并发性能再做选择

  • 进一步深度学习
sync.map的实现原理
  • 好文推荐