golang源码阅读-sync.map

1. sync.map简介

golang内置的map不是并发安全的,在v1.9版本提供了并发安全的map: sync.map

2. 简单使用

func TestSyncMap(t *testing.T) {
	m := sync.Map{}
	m.Store("name", "xiaoming")
	if item, ok := m.Load("name"); ok {
		fmt.Println(item)
	}
	m.Delete("name")
}

sync.map的使用方式和map类似,数据存储、获取、删除和遍历

3. sync.map设计思想

如果使用map 加锁进行数据操作时,耗时相对较多,sync.map采用了对数据进行读写分离,读时不加锁,用空间换时间

4.  sync.map

type Map struct {
// 锁
	mu Mutex
// 存储仅读数据
	read atomic.Value // readOnly
// 存储写入数据
	dirty map[any]*entry
// 计数器
	misses int
}

进行read操作时,不需要加锁,数据类型为atomic.Value,保证了数据的获取和将dirty赋值给read时的原子性,数据存储的是readOnly, readOnly内部存储的是值的指针,和dirty值的指针一致

5. 数据存储

func (m *Map) Store(key, value any) {
// 先从read中查找key
// 如果找到数据则更新数据, read和dirty数据存储的都是指针,修改其中一个值,另外一个同时被修改
// 如果在read中数据被标记为删除,则继续查找
	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 {
// 如果read的数据被标记为删除,则将数据设置为nil
		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.
// 老数据存储到dirty
			m.dirty[key] = e
		}
// 存储最新数据
		e.storeLocked(&value)
	} else if e, ok := m.dirty[key]; ok {
// read中没有,dirty中有对应的key,更新数据
		e.storeLocked(&value)
	} else {
// read和dirty都没有时
// amended 表示read和dirty数据是否不一致
// 数据一致时
		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.
// 将read未被标记删除的数据,刷新到dirty中
			m.dirtyLocked()
// 
			m.read.Store(readOnly{m: read.m, amended: true})
		}
// dirty存储新值
		m.dirty[key] = newEntry(value)
	}
	m.mu.Unlock()
}

6. 数据获取

func (m *Map) Load(key any) (value any, ok bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
// 数据不在read中,并且read和dirty数据不一致
	if !ok && read.amended {
		m.mu.Lock()
		// Avoid reporting a spurious miss if m.dirty got promoted while we were
		// blocked on m.mu. (If further loads of the same key will not miss, it's
		// not worth copying the dirty map for this key.)
// 双检查
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
// 直接从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.
// 每次在read中没有查到并且两者数据不一致,进行计数,达到阈值后,将dirty复制给read, dirty设置为nil
			m.missLocked()
		}
		m.mu.Unlock()
	}
	if !ok {
		return nil, false
	}
// 返回存在的数据
	return e.load()
}

7. 数据删除

func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
// read中没有,并且read和dirty数据不一致
	if !ok && read.amended {
		m.mu.Lock()
// 加锁后 双检查
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
// 直接删除dirty
			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.
// read中数据不存在并且 read和dirty数据不一致进行计数,达到阈值后,将dirty复制给read,dirty设置为nil
			m.missLocked()
		}
		m.mu.Unlock()
	}
	if ok {
		return e.delete()
	}
	return nil, false
}

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

余额充值