Source file src/sync/map.go

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build !goexperiment.synchashtriemap
     6  
     7  package sync
     8  
     9  import (
    10  	"sync/atomic"
    11  )
    12  
    13  // Map is like a Go map[any]any but is safe for concurrent use
    14  // by multiple goroutines without additional locking or coordination.
    15  // Loads, stores, and deletes run in amortized constant time.
    16  //
    17  // The Map type is specialized. Most code should use a plain Go map instead,
    18  // with separate locking or coordination, for better type safety and to make it
    19  // easier to maintain other invariants along with the map content.
    20  //
    21  // The Map type is optimized for two common use cases: (1) when the entry for a given
    22  // key is only ever written once but read many times, as in caches that only grow,
    23  // or (2) when multiple goroutines read, write, and overwrite entries for disjoint
    24  // sets of keys. In these two cases, use of a Map may significantly reduce lock
    25  // contention compared to a Go map paired with a separate [Mutex] or [RWMutex].
    26  //
    27  // The zero Map is empty and ready for use. A Map must not be copied after first use.
    28  //
    29  // In the terminology of [the Go memory model], Map arranges that a write operation
    30  // “synchronizes before” any read operation that observes the effect of the write, where
    31  // read and write operations are defined as follows.
    32  // [Map.Load], [Map.LoadAndDelete], [Map.LoadOrStore], [Map.Swap], [Map.CompareAndSwap],
    33  // and [Map.CompareAndDelete] are read operations;
    34  // [Map.Delete], [Map.LoadAndDelete], [Map.Store], and [Map.Swap] are write operations;
    35  // [Map.LoadOrStore] is a write operation when it returns loaded set to false;
    36  // [Map.CompareAndSwap] is a write operation when it returns swapped set to true;
    37  // and [Map.CompareAndDelete] is a write operation when it returns deleted set to true.
    38  //
    39  // [the Go memory model]: https://go.dev/ref/mem
    40  type Map struct {
    41  	_ noCopy
    42  
    43  	mu Mutex
    44  
    45  	// read contains the portion of the map's contents that are safe for
    46  	// concurrent access (with or without mu held).
    47  	//
    48  	// The read field itself is always safe to load, but must only be stored with
    49  	// mu held.
    50  	//
    51  	// Entries stored in read may be updated concurrently without mu, but updating
    52  	// a previously-expunged entry requires that the entry be copied to the dirty
    53  	// map and unexpunged with mu held.
    54  	read atomic.Pointer[readOnly]
    55  
    56  	// dirty contains the portion of the map's contents that require mu to be
    57  	// held. To ensure that the dirty map can be promoted to the read map quickly,
    58  	// it also includes all of the non-expunged entries in the read map.
    59  	//
    60  	// Expunged entries are not stored in the dirty map. An expunged entry in the
    61  	// clean map must be unexpunged and added to the dirty map before a new value
    62  	// can be stored to it.
    63  	//
    64  	// If the dirty map is nil, the next write to the map will initialize it by
    65  	// making a shallow copy of the clean map, omitting stale entries.
    66  	dirty map[any]*entry
    67  
    68  	// misses counts the number of loads since the read map was last updated that
    69  	// needed to lock mu to determine whether the key was present.
    70  	//
    71  	// Once enough misses have occurred to cover the cost of copying the dirty
    72  	// map, the dirty map will be promoted to the read map (in the unamended
    73  	// state) and the next store to the map will make a new dirty copy.
    74  	misses int
    75  }
    76  
    77  // readOnly is an immutable struct stored atomically in the Map.read field.
    78  type readOnly struct {
    79  	m       map[any]*entry
    80  	amended bool // true if the dirty map contains some key not in m.
    81  }
    82  
    83  // expunged is an arbitrary pointer that marks entries which have been deleted
    84  // from the dirty map.
    85  var expunged = new(any)
    86  
    87  // An entry is a slot in the map corresponding to a particular key.
    88  type entry struct {
    89  	// p points to the interface{} value stored for the entry.
    90  	//
    91  	// If p == nil, the entry has been deleted, and either m.dirty == nil or
    92  	// m.dirty[key] is e.
    93  	//
    94  	// If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
    95  	// is missing from m.dirty.
    96  	//
    97  	// Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty
    98  	// != nil, in m.dirty[key].
    99  	//
   100  	// An entry can be deleted by atomic replacement with nil: when m.dirty is
   101  	// next created, it will atomically replace nil with expunged and leave
   102  	// m.dirty[key] unset.
   103  	//
   104  	// An entry's associated value can be updated by atomic replacement, provided
   105  	// p != expunged. If p == expunged, an entry's associated value can be updated
   106  	// only after first setting m.dirty[key] = e so that lookups using the dirty
   107  	// map find the entry.
   108  	p atomic.Pointer[any]
   109  }
   110  
   111  func newEntry(i any) *entry {
   112  	e := &entry{}
   113  	e.p.Store(&i)
   114  	return e
   115  }
   116  
   117  func (m *Map) loadReadOnly() readOnly {
   118  	if p := m.read.Load(); p != nil {
   119  		return *p
   120  	}
   121  	return readOnly{}
   122  }
   123  
   124  // Load returns the value stored in the map for a key, or nil if no
   125  // value is present.
   126  // The ok result indicates whether value was found in the map.
   127  func (m *Map) Load(key any) (value any, ok bool) {
   128  	read := m.loadReadOnly()
   129  	e, ok := read.m[key]
   130  	if !ok && read.amended {
   131  		m.mu.Lock()
   132  		// Avoid reporting a spurious miss if m.dirty got promoted while we were
   133  		// blocked on m.mu. (If further loads of the same key will not miss, it's
   134  		// not worth copying the dirty map for this key.)
   135  		read = m.loadReadOnly()
   136  		e, ok = read.m[key]
   137  		if !ok && read.amended {
   138  			e, ok = m.dirty[key]
   139  			// Regardless of whether the entry was present, record a miss: this key
   140  			// will take the slow path until the dirty map is promoted to the read
   141  			// map.
   142  			m.missLocked()
   143  		}
   144  		m.mu.Unlock()
   145  	}
   146  	if !ok {
   147  		return nil, false
   148  	}
   149  	return e.load()
   150  }
   151  
   152  func (e *entry) load() (value any, ok bool) {
   153  	p := e.p.Load()
   154  	if p == nil || p == expunged {
   155  		return nil, false
   156  	}
   157  	return *p, true
   158  }
   159  
   160  // Store sets the value for a key.
   161  func (m *Map) Store(key, value any) {
   162  	_, _ = m.Swap(key, value)
   163  }
   164  
   165  // Clear deletes all the entries, resulting in an empty Map.
   166  func (m *Map) Clear() {
   167  	read := m.loadReadOnly()
   168  	if len(read.m) == 0 && !read.amended {
   169  		// Avoid allocating a new readOnly when the map is already clear.
   170  		return
   171  	}
   172  
   173  	m.mu.Lock()
   174  	defer m.mu.Unlock()
   175  
   176  	read = m.loadReadOnly()
   177  	if len(read.m) > 0 || read.amended {
   178  		m.read.Store(&readOnly{})
   179  	}
   180  
   181  	clear(m.dirty)
   182  	// Don't immediately promote the newly-cleared dirty map on the next operation.
   183  	m.misses = 0
   184  }
   185  
   186  // tryCompareAndSwap compare the entry with the given old value and swaps
   187  // it with a new value if the entry is equal to the old value, and the entry
   188  // has not been expunged.
   189  //
   190  // If the entry is expunged, tryCompareAndSwap returns false and leaves
   191  // the entry unchanged.
   192  func (e *entry) tryCompareAndSwap(old, new any) bool {
   193  	p := e.p.Load()
   194  	if p == nil || p == expunged || *p != old {
   195  		return false
   196  	}
   197  
   198  	// Copy the interface after the first load to make this method more amenable
   199  	// to escape analysis: if the comparison fails from the start, we shouldn't
   200  	// bother heap-allocating an interface value to store.
   201  	nc := new
   202  	for {
   203  		if e.p.CompareAndSwap(p, &nc) {
   204  			return true
   205  		}
   206  		p = e.p.Load()
   207  		if p == nil || p == expunged || *p != old {
   208  			return false
   209  		}
   210  	}
   211  }
   212  
   213  // unexpungeLocked ensures that the entry is not marked as expunged.
   214  //
   215  // If the entry was previously expunged, it must be added to the dirty map
   216  // before m.mu is unlocked.
   217  func (e *entry) unexpungeLocked() (wasExpunged bool) {
   218  	return e.p.CompareAndSwap(expunged, nil)
   219  }
   220  
   221  // swapLocked unconditionally swaps a value into the entry.
   222  //
   223  // The entry must be known not to be expunged.
   224  func (e *entry) swapLocked(i *any) *any {
   225  	return e.p.Swap(i)
   226  }
   227  
   228  // LoadOrStore returns the existing value for the key if present.
   229  // Otherwise, it stores and returns the given value.
   230  // The loaded result is true if the value was loaded, false if stored.
   231  func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool) {
   232  	// Avoid locking if it's a clean hit.
   233  	read := m.loadReadOnly()
   234  	if e, ok := read.m[key]; ok {
   235  		actual, loaded, ok := e.tryLoadOrStore(value)
   236  		if ok {
   237  			return actual, loaded
   238  		}
   239  	}
   240  
   241  	m.mu.Lock()
   242  	read = m.loadReadOnly()
   243  	if e, ok := read.m[key]; ok {
   244  		if e.unexpungeLocked() {
   245  			m.dirty[key] = e
   246  		}
   247  		actual, loaded, _ = e.tryLoadOrStore(value)
   248  	} else if e, ok := m.dirty[key]; ok {
   249  		actual, loaded, _ = e.tryLoadOrStore(value)
   250  		m.missLocked()
   251  	} else {
   252  		if !read.amended {
   253  			// We're adding the first new key to the dirty map.
   254  			// Make sure it is allocated and mark the read-only map as incomplete.
   255  			m.dirtyLocked()
   256  			m.read.Store(&readOnly{m: read.m, amended: true})
   257  		}
   258  		m.dirty[key] = newEntry(value)
   259  		actual, loaded = value, false
   260  	}
   261  	m.mu.Unlock()
   262  
   263  	return actual, loaded
   264  }
   265  
   266  // tryLoadOrStore atomically loads or stores a value if the entry is not
   267  // expunged.
   268  //
   269  // If the entry is expunged, tryLoadOrStore leaves the entry unchanged and
   270  // returns with ok==false.
   271  func (e *entry) tryLoadOrStore(i any) (actual any, loaded, ok bool) {
   272  	p := e.p.Load()
   273  	if p == expunged {
   274  		return nil, false, false
   275  	}
   276  	if p != nil {
   277  		return *p, true, true
   278  	}
   279  
   280  	// Copy the interface after the first load to make this method more amenable
   281  	// to escape analysis: if we hit the "load" path or the entry is expunged, we
   282  	// shouldn't bother heap-allocating.
   283  	ic := i
   284  	for {
   285  		if e.p.CompareAndSwap(nil, &ic) {
   286  			return i, false, true
   287  		}
   288  		p = e.p.Load()
   289  		if p == expunged {
   290  			return nil, false, false
   291  		}
   292  		if p != nil {
   293  			return *p, true, true
   294  		}
   295  	}
   296  }
   297  
   298  // LoadAndDelete deletes the value for a key, returning the previous value if any.
   299  // The loaded result reports whether the key was present.
   300  func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
   301  	read := m.loadReadOnly()
   302  	e, ok := read.m[key]
   303  	if !ok && read.amended {
   304  		m.mu.Lock()
   305  		read = m.loadReadOnly()
   306  		e, ok = read.m[key]
   307  		if !ok && read.amended {
   308  			e, ok = m.dirty[key]
   309  			delete(m.dirty, key)
   310  			// Regardless of whether the entry was present, record a miss: this key
   311  			// will take the slow path until the dirty map is promoted to the read
   312  			// map.
   313  			m.missLocked()
   314  		}
   315  		m.mu.Unlock()
   316  	}
   317  	if ok {
   318  		return e.delete()
   319  	}
   320  	return nil, false
   321  }
   322  
   323  // Delete deletes the value for a key.
   324  func (m *Map) Delete(key any) {
   325  	m.LoadAndDelete(key)
   326  }
   327  
   328  func (e *entry) delete() (value any, ok bool) {
   329  	for {
   330  		p := e.p.Load()
   331  		if p == nil || p == expunged {
   332  			return nil, false
   333  		}
   334  		if e.p.CompareAndSwap(p, nil) {
   335  			return *p, true
   336  		}
   337  	}
   338  }
   339  
   340  // trySwap swaps a value if the entry has not been expunged.
   341  //
   342  // If the entry is expunged, trySwap returns false and leaves the entry
   343  // unchanged.
   344  func (e *entry) trySwap(i *any) (*any, bool) {
   345  	for {
   346  		p := e.p.Load()
   347  		if p == expunged {
   348  			return nil, false
   349  		}
   350  		if e.p.CompareAndSwap(p, i) {
   351  			return p, true
   352  		}
   353  	}
   354  }
   355  
   356  // Swap swaps the value for a key and returns the previous value if any.
   357  // The loaded result reports whether the key was present.
   358  func (m *Map) Swap(key, value any) (previous any, loaded bool) {
   359  	read := m.loadReadOnly()
   360  	if e, ok := read.m[key]; ok {
   361  		if v, ok := e.trySwap(&value); ok {
   362  			if v == nil {
   363  				return nil, false
   364  			}
   365  			return *v, true
   366  		}
   367  	}
   368  
   369  	m.mu.Lock()
   370  	read = m.loadReadOnly()
   371  	if e, ok := read.m[key]; ok {
   372  		if e.unexpungeLocked() {
   373  			// The entry was previously expunged, which implies that there is a
   374  			// non-nil dirty map and this entry is not in it.
   375  			m.dirty[key] = e
   376  		}
   377  		if v := e.swapLocked(&value); v != nil {
   378  			loaded = true
   379  			previous = *v
   380  		}
   381  	} else if e, ok := m.dirty[key]; ok {
   382  		if v := e.swapLocked(&value); v != nil {
   383  			loaded = true
   384  			previous = *v
   385  		}
   386  	} else {
   387  		if !read.amended {
   388  			// We're adding the first new key to the dirty map.
   389  			// Make sure it is allocated and mark the read-only map as incomplete.
   390  			m.dirtyLocked()
   391  			m.read.Store(&readOnly{m: read.m, amended: true})
   392  		}
   393  		m.dirty[key] = newEntry(value)
   394  	}
   395  	m.mu.Unlock()
   396  	return previous, loaded
   397  }
   398  
   399  // CompareAndSwap swaps the old and new values for key
   400  // if the value stored in the map is equal to old.
   401  // The old value must be of a comparable type.
   402  func (m *Map) CompareAndSwap(key, old, new any) (swapped bool) {
   403  	read := m.loadReadOnly()
   404  	if e, ok := read.m[key]; ok {
   405  		return e.tryCompareAndSwap(old, new)
   406  	} else if !read.amended {
   407  		return false // No existing value for key.
   408  	}
   409  
   410  	m.mu.Lock()
   411  	defer m.mu.Unlock()
   412  	read = m.loadReadOnly()
   413  	swapped = false
   414  	if e, ok := read.m[key]; ok {
   415  		swapped = e.tryCompareAndSwap(old, new)
   416  	} else if e, ok := m.dirty[key]; ok {
   417  		swapped = e.tryCompareAndSwap(old, new)
   418  		// We needed to lock mu in order to load the entry for key,
   419  		// and the operation didn't change the set of keys in the map
   420  		// (so it would be made more efficient by promoting the dirty
   421  		// map to read-only).
   422  		// Count it as a miss so that we will eventually switch to the
   423  		// more efficient steady state.
   424  		m.missLocked()
   425  	}
   426  	return swapped
   427  }
   428  
   429  // CompareAndDelete deletes the entry for key if its value is equal to old.
   430  // The old value must be of a comparable type.
   431  //
   432  // If there is no current value for key in the map, CompareAndDelete
   433  // returns false (even if the old value is the nil interface value).
   434  func (m *Map) CompareAndDelete(key, old any) (deleted bool) {
   435  	read := m.loadReadOnly()
   436  	e, ok := read.m[key]
   437  	if !ok && read.amended {
   438  		m.mu.Lock()
   439  		read = m.loadReadOnly()
   440  		e, ok = read.m[key]
   441  		if !ok && read.amended {
   442  			e, ok = m.dirty[key]
   443  			// Don't delete key from m.dirty: we still need to do the “compare” part
   444  			// of the operation. The entry will eventually be expunged when the
   445  			// dirty map is promoted to the read map.
   446  			//
   447  			// Regardless of whether the entry was present, record a miss: this key
   448  			// will take the slow path until the dirty map is promoted to the read
   449  			// map.
   450  			m.missLocked()
   451  		}
   452  		m.mu.Unlock()
   453  	}
   454  	for ok {
   455  		p := e.p.Load()
   456  		if p == nil || p == expunged || *p != old {
   457  			return false
   458  		}
   459  		if e.p.CompareAndSwap(p, nil) {
   460  			return true
   461  		}
   462  	}
   463  	return false
   464  }
   465  
   466  // Range calls f sequentially for each key and value present in the map.
   467  // If f returns false, range stops the iteration.
   468  //
   469  // Range does not necessarily correspond to any consistent snapshot of the Map's
   470  // contents: no key will be visited more than once, but if the value for any key
   471  // is stored or deleted concurrently (including by f), Range may reflect any
   472  // mapping for that key from any point during the Range call. Range does not
   473  // block other methods on the receiver; even f itself may call any method on m.
   474  //
   475  // Range may be O(N) with the number of elements in the map even if f returns
   476  // false after a constant number of calls.
   477  func (m *Map) Range(f func(key, value any) bool) {
   478  	// We need to be able to iterate over all of the keys that were already
   479  	// present at the start of the call to Range.
   480  	// If read.amended is false, then read.m satisfies that property without
   481  	// requiring us to hold m.mu for a long time.
   482  	read := m.loadReadOnly()
   483  	if read.amended {
   484  		// m.dirty contains keys not in read.m. Fortunately, Range is already O(N)
   485  		// (assuming the caller does not break out early), so a call to Range
   486  		// amortizes an entire copy of the map: we can promote the dirty copy
   487  		// immediately!
   488  		m.mu.Lock()
   489  		read = m.loadReadOnly()
   490  		if read.amended {
   491  			read = readOnly{m: m.dirty}
   492  			copyRead := read
   493  			m.read.Store(&copyRead)
   494  			m.dirty = nil
   495  			m.misses = 0
   496  		}
   497  		m.mu.Unlock()
   498  	}
   499  
   500  	for k, e := range read.m {
   501  		v, ok := e.load()
   502  		if !ok {
   503  			continue
   504  		}
   505  		if !f(k, v) {
   506  			break
   507  		}
   508  	}
   509  }
   510  
   511  func (m *Map) missLocked() {
   512  	m.misses++
   513  	if m.misses < len(m.dirty) {
   514  		return
   515  	}
   516  	m.read.Store(&readOnly{m: m.dirty})
   517  	m.dirty = nil
   518  	m.misses = 0
   519  }
   520  
   521  func (m *Map) dirtyLocked() {
   522  	if m.dirty != nil {
   523  		return
   524  	}
   525  
   526  	read := m.loadReadOnly()
   527  	m.dirty = make(map[any]*entry, len(read.m))
   528  	for k, e := range read.m {
   529  		if !e.tryExpungeLocked() {
   530  			m.dirty[k] = e
   531  		}
   532  	}
   533  }
   534  
   535  func (e *entry) tryExpungeLocked() (isExpunged bool) {
   536  	p := e.p.Load()
   537  	for p == nil {
   538  		if e.p.CompareAndSwap(nil, expunged) {
   539  			return true
   540  		}
   541  		p = e.p.Load()
   542  	}
   543  	return p == expunged
   544  }
   545  

View as plain text