go src - sync.Map_环球速递
前言在并发编程中,我们经常会遇到多个goroutine同时操作一个map的情况
博客园 2023-07-02 05:19:31
在并发编程中,我们经常会遇到多个goroutine同时操作一个map的情况。如果在这种情况下直接使用普通的map,那么就可能会引发竞态条件,造成数据不一致或者更严重的问题。
sync.Map
是Go语言中内置的一种并发安全的map,但是他的实现和用法与普通的map完全不同,这篇文章将详细介绍这些区别。
创建sync.Map
非常简单,只需要声明即可:
(资料图片)
var m sync.Map
使用Store
方法存储键值对:
m.Store("hello", "world")
使用Load
方法获取值:
value, ok := m.Load("hello")if ok { fmt.Println(value) // 输出:world}
使用Delete
方法删除键值对:
m.Delete("hello")
sync.Map
的核心实现依赖于两个主要的数据结构:一个只读的read
字段,以及一个可写的dirty
字段。
当我们进行读取操作(Load
)时,首先会尝试从read
字段读取数据,这个过程是完全无锁的。
如果read
中没有找到,那么会尝试加锁后从dirty
中读取。这个设计保证了在大部分读多写少的场景下,读操作是无锁的,大大提升了性能。
写入操作(key不存在
)时,会直接在dirty
中进行写入,并将read
的amended
标记为true
,表示read
字段有待更新的数据。
然后再有新的读取操作到来时,如果amended
为true并且miss数量超过dirty长度
,则会从dirty
中拷贝数据到read
,并清除amended
标记。
在这个设计中,读操作在大部分情况下是无锁的,而写操作(key不存在时)则需要获取dirty
的锁,从而实现了对于读多写少场景的优化。
sync.Map
在以下两种情况下表现得特别好:
这是因为sync.Map
的设计将读取操作优化至极致,同时尽量减少在写入新键值对时的锁竞争。
然而,sync.Map
并不是银弹,也有一些局限:
sync.Map
没有像普通map那样的直观语法,必须使用特定的方法来操作键值对sync.Map
的性能可能不如使用普通map加锁的方式type Map struct {mu Mutex// read contains the portion of the map"s contents that are safe for// concurrent access (with or without mu held).read atomic.Pointer[readOnly]// dirty contains the portion of the map"s contents that require mu to be// held. To ensure that the dirty map can be promoted to the read map quickly,// it also includes all of the non-expunged entries in the read map.dirty map[any]*entry// misses counts the number of loads since the read map was last updated that// needed to lock mu to determine whether the key was present.misses int}
// readOnly is an immutable struct stored atomically in the Map.read field.type readOnly struct {m map[any]*entryamended bool // true if the dirty map contains some key not in m.}
// An entry is a slot in the map corresponding to a particular key.type entry struct {// p points to the interface{} value stored for the entry.p atomic.Pointer[any]}
// expunged is an arbitrary pointer that marks entries which have been deleted// from the dirty map.var expunged = new(any)
状态机:
总结:
// Load returns the value stored in the map for a key, or nil if no// value is present.// The ok result indicates whether value was found in the map.func (m *Map) Load(key any) (value any, ok bool) {read := m.loadReadOnly()e, ok := read.m[key]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.loadReadOnly()e, ok = read.m[key]if !ok && read.amended {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()}m.mu.Unlock()}if !ok {return nil, false}return e.load()}func (m *Map) loadReadOnly() readOnly {if p := m.read.Load(); p != nil {return *p}return readOnly{}}
总结:
// LoadOrStore returns the existing value for the key if present.// Otherwise, it stores and returns the given value.// The loaded result is true if the value was loaded, false if stored.func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool) {// Avoid locking if it"s a clean hit.read := m.loadReadOnly()if e, ok := read.m[key]; ok {actual, loaded, ok := e.tryLoadOrStore(value)if ok {return actual, loaded}}m.mu.Lock()read = m.loadReadOnly()if e, ok := read.m[key]; ok {if e.unexpungeLocked() {m.dirty[key] = e}actual, loaded, _ = e.tryLoadOrStore(value)} else if e, ok := m.dirty[key]; ok {actual, loaded, _ = e.tryLoadOrStore(value)m.missLocked()} 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)actual, loaded = value, false}m.mu.Unlock()return actual, loaded}// tryLoadOrStore atomically loads or stores a value if the entry is not// expunged.//// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and// returns with ok==false.func (e *entry) tryLoadOrStore(i any) (actual any, loaded, ok bool) {p := e.p.Load()if p == expunged {return nil, false, false}if p != nil {return *p, true, true}// Copy the interface after the first load to make this method more amenable// to escape analysis: if we hit the "load" path or the entry is expunged, we// shouldn"t bother heap-allocating.ic := ifor {if e.p.CompareAndSwap(nil, &ic) {return i, false, true}p = e.p.Load()if p == expunged {return nil, false, false}if p != nil {return *p, true, true}}}
总结:
// Delete deletes the value for a key.func (m *Map) Delete(key any) {m.LoadAndDelete(key)}
// LoadAndDelete deletes the value for a key, returning the previous value if any.// The loaded result reports whether the key was present.func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {read := m.loadReadOnly()e, ok := read.m[key]if !ok && read.amended {m.mu.Lock()read = m.loadReadOnly()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}
总结:
// Store sets the value for a key.func (m *Map) Store(key, value any) {_, _ = m.Swap(key, value)}// Swap swaps the value for a key and returns the previous value if any.// The loaded result reports whether the key was present.func (m *Map) Swap(key, value any) (previous any, loaded bool) {read := m.loadReadOnly()if e, ok := read.m[key]; ok {if v, ok := e.trySwap(&value); ok {if v == nil {return nil, false}return *v, true}}m.mu.Lock()read = m.loadReadOnly()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}if v := e.swapLocked(&value); v != nil {loaded = trueprevious = *v}} else if e, ok := m.dirty[key]; ok {if v := e.swapLocked(&value); v != nil {loaded = trueprevious = *v}} 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()return previous, loaded}// trySwap swaps a value if the entry has not been expunged.//// If the entry is expunged, trySwap returns false and leaves the entry// unchanged.func (e *entry) trySwap(i *any) (*any, bool) {for {p := e.p.Load()if p == expunged {return nil, false}if e.p.CompareAndSwap(p, i) {return p, true}}}
总结:
// CompareAndSwap swaps the old and new values for key// if the value stored in the map is equal to old.// The old value must be of a comparable type.func (m *Map) CompareAndSwap(key, old, new any) bool {read := m.loadReadOnly()if e, ok := read.m[key]; ok {return e.tryCompareAndSwap(old, new)} else if !read.amended {return false // No existing value for key.}m.mu.Lock()defer m.mu.Unlock()read = m.loadReadOnly()swapped := falseif e, ok := read.m[key]; ok {swapped = e.tryCompareAndSwap(old, new)} else if e, ok := m.dirty[key]; ok {swapped = e.tryCompareAndSwap(old, new)// We needed to lock mu in order to load the entry for key,// and the operation didn"t change the set of keys in the map// (so it would be made more efficient by promoting the dirty// map to read-only).// Count it as a miss so that we will eventually switch to the// more efficient steady state.m.missLocked()}return swapped}// tryCompareAndSwap compare the entry with the given old value and swaps// it with a new value if the entry is equal to the old value, and the entry// has not been expunged.//// If the entry is expunged, tryCompareAndSwap returns false and leaves// the entry unchanged.func (e *entry) tryCompareAndSwap(old, new any) bool {p := e.p.Load()if p == nil || p == expunged || *p != old {return false}// Copy the interface after the first load to make this method more amenable// to escape analysis: if the comparison fails from the start, we shouldn"t// bother heap-allocating an interface value to store.nc := newfor {if e.p.CompareAndSwap(p, &nc) {return true}p = e.p.Load()if p == nil || p == expunged || *p != old {return false}}}
总结:
// CompareAndDelete deletes the entry for key if its value is equal to old.// The old value must be of a comparable type.//// If there is no current value for key in the map, CompareAndDelete// returns false (even if the old value is the nil interface value).func (m *Map) CompareAndDelete(key, old any) (deleted bool) {read := m.loadReadOnly()e, ok := read.m[key]if !ok && read.amended {m.mu.Lock()read = m.loadReadOnly()e, ok = read.m[key]if !ok && read.amended {e, ok = m.dirty[key]// Don"t delete key from m.dirty: we still need to do the “compare” part// of the operation. The entry will eventually be expunged when the// dirty map is promoted to the read map.//// 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()}for ok {p := e.p.Load()if p == nil || p == expunged || *p != old {return false}if e.p.CompareAndSwap(p, nil) {return true}}return false}
总结:
结论
总的来说,sync.Map
是Go标准库提供的一个非常有用的工具,它可以帮助我们简化并发编程,并且在一些特定的场景下能提供良好的性能。
但在使用的时候,我们需要根据具体的应用场景和需求来选择使用sync.Map
还是其他的并发原语。
前言在并发编程中,我们经常会遇到多个goroutine同时操作一个map的情况
30日,“泉海强安”海上危化品事故应急综合演练在福建省泉州市斗尾港区
罗马诺再次用标志性的Herewego确认,布罗佐维奇将加盟利雅得胜利,转会
hello大家好,我是价值网小科来为大家解答以上问题,烷烃烯烃炔烃的通
说到德系豪华品牌,可以说是燃油车时代的一线豪车,不管是在销量还是在
暑假临近,重庆各大高校即将放假。上游新闻记者整理了各高校放假时间。
最近玩小伙伴的玩家不知道有没有发现,以前玩游戏,参考一些攻略,就能
原标题:千亿级母基金密集启航:江西3000亿产业基金落地澎湃新闻记者戚
6月30日,全国性大宗商品仓单注册登记中心(以下简称“全仓登”)上海
相信大家对SAI不陌生吧?目前数码绘画行业内比较为人熟知的各种画风都
内容正在升级改造,请稍后再试!【免责声明】本文仅代表合作供稿方观点
AnnapurnaInteractive宣布了其首款内部开发的游戏《银翼杀手2033:迷宫
1、建行北京中关村分行青年志愿者服务队是由建行北京青年志愿者服务队
1、这个得看你弄多久的,有一年的话,应该不会。2、我的弄一年,但是会
21世纪经济报道记者高江虹北京报道中国国家铁路集团有限公司(以下简称