sync.Map理解

sync.Map的结构:

type Map struct {
	mu Mutex
	
    // sync.Map结构体主要分为两个部分,read主要用于读,dirty记录read中缺失的k-v
	read atomic.Value // readOnly
	
	dirty map[any]*entry

    // misses 记录访问时miss的次数
	misses int
}

// Map.read(Value类型,可以断言为readOnly)和 Map.dirty本质上是同一类型的,read只是多了一个变量记录是否存在dirty有而read没有的k-v
type readOnly struct {
	m       map[any]*entry
	amended bool // true if the dirty map contains some key not in m.
}

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

// 字面意思擦除,表示不存在dirty中
var expunged = unsafe.Pointer(new(any))

主要关注Load,Store和LoadAndDelete三个方法:

Load

func (m *Map) Load(key any) (value any, ok bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
	if !ok && read.amended {
		m.mu.Lock()
		// double check,防止加锁期间m出现变化,如本来没有的键,在该协程(检查完之后)获得锁之前,其他协程写入了这个键值
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
			e, ok = m.dirty[key]
			// 如果read中找不到key,就去dirty中找,missLoad的作用是misses++,如果misses==dirty长度,那么把dirty提升为read。注意实际的load其实是有e.load()完成的。
			m.missLocked()
		}
		m.mu.Unlock()
	}
	if !ok {
		return nil, false
	}
	return e.load()
}

func (e *entry) load() (value any, ok bool) {
	p := atomic.LoadPointer(&e.p)
	if p == nil || p == expunged { 
		return nil, false
	}
	return *(*any)(p), true
}

如果能在read中读到数据(ok==true),可以跳过分支

如果读不到,且amended表明数据可能在dirty中,则到dirty中去读。注意上锁后double check以及增加misses计数

最后如果read和dirty中都读不到(ok==false),返回false,否则用e.load读取数据

Store

func (m *Map) Store(key, value any) {
	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() {
            // 如果之前这个entry的状态是擦除,那么dirty[key]重新指向e
			m.dirty[key] = e
		}
		e.storeLocked(&value)
	} else if e, ok := m.dirty[key]; ok {
        // 如果read中找不到,到dirty中寻找
		e.storeLocked(&value)
	} else {
		if !read.amended {
			// 如果amended之前不是false,那么设置amended为true;如果dirty为nil,创建一个dirty
			m.dirtyLocked()
			m.read.Store(readOnly{m: read.m, amended: true})
		}
        // 注意read和dirty都miss的情况下,只存在dirty中
		m.dirty[key] = newEntry(value)
	}
	m.mu.Unlock()
}

如果read中能读到数据,能成功写入(tryStore),则结束

如果read读到,但是是expunge状态,则dirty中也重新指向同一个e,同时写入value

如果read读不到,dirty读到,在dirty中写入

如果都读不到,需要在dirty中插入。此时,如果amended==false,则需要更新为true,如果dirty还是nil,则需要创建一个dirty,具体做法是初始化一个数组,然后key的entry指向read[key](即在创建之初就让dirty和read相同的key指向同一个entry)。

LoadAndDelete

func (m *Map) LoadAndDelete(key any) (value any, 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)
			m.missLocked()
		}
		m.mu.Unlock()
	}
	if ok {
		return e.delete()
	}
	return nil, false
}

func (e *entry) delete() (value any, ok bool) {
	for {
		p := atomic.LoadPointer(&e.p)
		if p == nil || p == expunged {
			return nil, false
		}
		if atomic.CompareAndSwapPointer(&e.p, p, nil) {
			return *(*any)(p), true
		}
	}
}

注意delete是在无锁环境下执行的,可能会给其他线程的造成影响,因此创建dirty时(即遍历read并把entry赋值给dirty)需要判断p是不是nil 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值