golang之syn.Map学习笔记

1.syn.Map特点

特点:golang提供的一种线程安全的map数据结构,通常存储&获取元素通Store,跟Load两个方法获取元素。syn.Map的核心思想是使用了两个map,一个用于读取read,另一个是用于写入dirty,这种思想类似缓存跟数据库之间设置类似,如上字段,read使用原子操作,作为读缓存,如果从read读取不倒数据,再从dirty中获取,从而达到快速的读取跟写入。该种设计思想构成资源无竞争情况,利用空间换时间的一种设计思路.详情见如下。

2.如下syn.Map接口定义&相关存储结构定义:

type mapInterface interface {
	Load(any) (any, bool)
	Store(key, value any)
	....
}

为什么syn.Map是线程安全的呢?见如下结构详解.

首先,Map的结构定义如下:

type Map struct {
	mu Mutex   // 锁,用于保护dirty 字段
	
	read atomic.Value // readOnly 类型结构,Value,为一个接口类型

	dirty map[any]*entry // 用于写入得map存储

	misses int  //计数器,每次需要读 dirty 则 +1,当misses >= len(m.dirty),将整个dirty中的数据存储到read中,再将计数器归零,重新开始一轮的统计,并且将当前的m.dirty = nil置空。
}

missess 操作方法:

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
}
type readOnly struct {
	m       map[any]*entry
	amended bool // 表示 dirty 里存在read里没有的 key,通过该字段决定是否加锁读dirty
}

type entry struct {
	p unsafe.Pointer // *interface{}
}

Store:

func (m *Map) Store(key, value any) {
	read, _ := m.read.Load().(readOnly)  // 从readOnly中先读取数据,检查是否存在,如果存在,尝试存储到entry里面
	if e, ok := read.m[key]; ok && e.tryStore(&value) {
		return
	}
	// 当前的key不存在,加锁
	m.mu.Lock()
	read, _ = m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok {  // 从read中读到了对应的key
		if e.unexpungeLocked() {
			m.dirty[key] = e // 当前的p == expunged,将读取到的entry存储至dirty中(为expunged的数据不会存在dirty中)
		}
		e.storeLocked(&value) // 用当前的直更新entry中的数据

	} else if e, ok := m.dirty[key]; ok { // 从dirty中读取数据,如果存在,直接用值更新
		e.storeLocked(&value)
	} else {
		// read 跟dirty中都没读到对应的数据,判断当前read.amended,true,表示已经存在key,false表示不存在key
		if !read.amended {

			m.dirtyLocked() // 检查是否已经分配了dirty内存结构,(dirty大小取决于len(read.m)的长度)
			m.read.Store(readOnly{m: read.m, amended: true}) // m.read构建一个readOnly结构存储,并设置标记
		}
		m.dirty[key] = newEntry(value) // 构建一份数据存入dirty中
	}
	m.mu.Unlock()
}

Load方法:

func (m *Map) Load(key any) (value any, ok bool) {
	read, _ := m.read.Load().(readOnly) // 先动read中读取数据,
	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 { // 发现read中确实没有,那就从dirty中读取
			e, ok = 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() // 每次从dirty读取一次,就+1,等到misses >= len(m.dirty),将整个dirty中的数据存储到read中,再将计数器归零,重新开始一轮的统计,并且将当前的m.dirty = nil置空。
		}
		m.mu.Unlock()
	}
	if !ok {
		return nil, false
	}
	return e.load() // 返回对应的value值
}

Delete: 

func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
	read, _ := m.read.Load().(readOnly) // 先从read中查询
	e, ok := read.m[key]
	if !ok && read.amended { 
		m.mu.Lock() // read没找到,加锁在从read找一遍
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {  则加锁从dirty中查询,并删除相关的k-v值
			e, ok = m.dirty[key]
			delete(m.dirty, key)
			m.missLocked() // 将dirty中的数据存储至read,便于其他删除操作直接从read查找进行删除处理
		}
		m.mu.Unlock()
	}
	if ok { // 找到了,直接删除即可
		return e.delete()
	}
	return nil, false
}

结论:综上方法实现总结发现,使用了读写分离的思想过程,避免了并发情况下写入的混乱问题。读取速度只要read存在存在对应数据,直接从中读取即可,非常适用于度多写少的场景.

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、付费专栏及课程。

余额充值