The data structure of sync.map is as follows
type Map struct{
mu Mutext // This lock is used for dirty operations
read atomic.Value // Read-only data
dirty map[interface{}]*entry// New values are placed in the dirty, and read is redundant
misses int// Read The median number of lost lives
}
Copy the code
The data structure of read is
type readOnly struct{
m map[interface{}]*entry
amended bool
}
Copy the code
The entry data structure contains a pointer to the value stored by the user
type entry struct{
p unsafe.Pointer
}
Copy the code
M and map. dirty store values of type *entry and are Pointers to the same data, so even though the value of the Map is large, the amount of redundant space is limited. A pointer to value is a pointer to value.
Use method load source code as follows
// 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 interface{}) (value interface{}, ok bool) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
if! ok && read.amended {// If there is no data in read, and dirty and read data are inconsistent, there may be
m.mu.Lock()// Lock the dirty
read, _ = m.read.Load().(readOnly)// Double check to see if there is any read in the lock, because the read data may change during the lock
e, ok = read.m[key]
if! ok && read.amended {// Read does not exist, dirty may exist
e, ok = m.dirty[key]
m.missLocked()// This is an extra operation, independent of the value, because read missed once, so this method miss++, up to a point, migrates dirty data to read
}
m.mu.Unlock()// Unlock
}
if! ok {It can be amended to be true or amended to be true or amended to be true or amended to be true or amended to be true or amended to be true
return nil, false
}
return e.load()// If the value is returned, it depends on the value
}
Copy the code
MissLocked’s source code is as follows. If there are too many Misses, it means that each time it looks for Read and cannot find dirty, and then it looks for dirty. This is done twice, and locks are locked, which is very inefficient
func (m *Map) missLocked() {
m.misses++// Get dirty once, miss + 1
if m.misses < len(m.dirty) {// The number of misses has not reached the number of elements in dirty
return
}
m.read.Store(readOnly{m: m.dirty})// There are too many misses. Read stores data in dirty
m.dirty = nil/ / dirty empty
m.misses = 0/ / miss to empty
}
Copy the code
* Entry load method source code is as follows
func (e *entry) load() (value interface{}, ok bool) {
p := atomic.LoadPointer(&e.p)// Remove the pointer
if p == nil || p == expunged {// We can find the address of the value corresponding to the key, but the data in the address has been deleted
return nil, false// Return false to indicate that it was not found
}
return *(*interface{})(p), true// find and return the specific value
}
Copy the code
The source code for the Store method is as follows
// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {// Update value if read contains and is not deleted
return
}
m.mu.Lock()// There are two cases where 1read is not present and 2read is present but deleted
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {// The second case read exists but has been deleted
if e.unexpungeLocked() {// If the deletion succeeded
m.dirty[key] = e// Assign to dirty
}
e.storeLocked(&value)// Assign value to e
} else if e, ok := m.dirty[key]; ok {// Read has no value, dirty has a value
e.storeLocked(&value)// Update the value
} else {// There are no values in read and dirty
if! read.amended {// If read and dirty data are consistent
m.dirtyLocked()When dirty is empty, copy undeleted data from read to dirty. If dirty is empty, do nothing
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)// Put the new key value in the dirty
}
m.mu.Unlock()
}
Copy the code
1. Update the value of the corresponding entry if there is one. 2. If the read is deleted, the deletion flag is removed and the value corresponding to the entry is updated. 3. If read does not exist and dirty does, the value of the corresponding entry is updated. The Store may copy data from m.read under certain circumstances (after initialization or after m. search has been promoted). If the amount of data in m.read is very high, it may affect performance. This part of the logic in the source code for storeLocked in the dirtyLocked method is to put the value in the entry structure
// The entry must be known not to be expunged.
func (e *entry) storeLocked(i *interface{}) {
atomic.StorePointer(&e.p, unsafe.Pointer(i))
}
Copy the code
func (m *Map) dirtyLocked() {
ifm.dirty ! = nil {return
}
read, _ := m.read.Load().(readOnly)
m.dirty = make(map[interface{}]*entry, len(read.m))
for k, e := range read.m {
if! e.tryExpungeLocked() {// Not the deleted element
m.dirty[k] = e// Copy to dirty}}}Copy the code
Delete the source
// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
if! ok && read.amended {// If read does not but dirty may
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if! ok && read.amended {// Double check if read still does not have dirty
delete(m.dirty, key)// Delete the dirty data from the database. If the dirty data does not exist, it will not be deleted
}
m.mu.Unlock()
}
if ok {// Read has a value
e.delete()// Soft delete, update entry corresponding to this value to nil}}Copy the code
Range traversal source code as follows
func (m *Map) Range(f func(key, value interface{}) bool) {
read, _ := m.read.Load().(readOnly)
if read.amended {// If there is new data in dirty
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
if read.amended {/ / double check
read = readOnly{m: m.dirty}// Give the value of dirty to read
m.read.Store(read)
m.dirty = nil/ / clear the dirty
m.misses = 0
}
m.mu.Unlock()
}
for k, e := range read.m {
v, ok := e.load()
if! ok {continue
}
if !f(k, v) {
break}}}Copy the code
Sync. Map has no Len method, and there is currently no indication to add, so if you want to get the number of valid entries in the current Map, you need to use the Range method once