Golang源码分析(十六) 并发安全Map

135 阅读13分钟

并发安全Map

1. 引言:并发Map的本质

传统Map的并发局限性分析

在并发编程中,我们经常需要在多个goroutine之间共享Map数据结构。然而,Go语言的内置map并不是并发安全的,在并发访问时会出现竞态条件甚至panic:

  • 内置map:读写操作不是原子的,并发访问会导致数据竞争
  • map + Mutex:虽然安全,但所有操作都需要加锁,性能瓶颈明显
  • map + RWMutex:读操作可以并发,但写操作仍然阻塞所有访问
  • 分片锁方案:复杂度高,需要处理rehash等问题

sync.Map解决的问题场景

sync.Map专门为解决"高并发读写Map"的性能问题而设计:

// 传统方案的性能瓶颈
type SafeMap struct {
    mu sync.RWMutex
    m  map[string]interface{}
}

func (sm *SafeMap) Load(key string) (interface{}, bool) {
    sm.mu.RLock()         // 即使是读操作也需要加锁
    defer sm.mu.RUnlock()
    v, ok := sm.m[key]
    return v, ok
}

// sync.Map的优化方案
var m sync.Map
value, ok := m.Load(key)  // 大多数情况下无锁读取

Go 语言中 sync.Map 的设计定位

Go的sync.Map为高并发场景提供了专门优化的解决方案,它具有以下特点:

  1. 读优化设计:针对"写少读多"场景进行深度优化
  2. 双Map架构:通过read/dirty双Map实现读写分离
  3. 原子操作优化:大量使用原子操作避免锁争用
  4. 渐进式同步:通过miss计数实现智能的Map切换

2. sync.Map 核心结构解析

sync.Map 公共接口结构

让我们首先分析sync.Map的核心结构:

// go/src/sync/map.go
type Map struct {
    // mu 保护dirty map的互斥锁
    mu Mutex

    // read 包含Map内容中可以并发安全访问的部分
    // read字段本身总是可以安全加载的,但只能在持有mu时存储
    // 
    // 存储在read中的条目可以在不持有mu的情况下并发更新,
    // 但更新先前被删除的条目需要将条目复制到dirty map并在持有mu时取消删除
    read atomic.Pointer[readOnly]

    // dirty 包含需要持有mu才能访问的Map内容部分
    // 为了确保dirty map可以快速提升为read map,
    // 它还包括read map中所有未被删除的条目
    //
    // 被删除的条目不存储在dirty map中。clean map中被删除的条目
    // 必须先取消删除并添加到dirty map中,然后才能存储新值
    //
    // 如果dirty map为nil,下一次写入将通过制作clean map的浅拷贝来初始化它,
    // 省略过时的条目
    dirty map[any]*entry

    // misses 计算自上次更新read map以来需要锁定mu来确定键是否存在的加载次数
    //
    // 一旦发生足够的misses来覆盖复制dirty map的成本,
    // dirty map将被提升为read map(处于未修改状态),
    // 下一次存储到map将创建新的dirty副本
    misses int
}

设计要点解析:

  1. mu字段:保护dirty map的互斥锁,只在必要时使用
  2. read字段:原子指针,指向只读Map,支持无锁并发读取
  3. dirty字段:可写Map,包含最新的数据,需要锁保护
  4. misses字段:miss计数器,用于触发read/dirty切换

readOnly 结构体

readOnly是存储在read字段中的不可变结构:

// readOnly 是存储在Map.read字段中的不可变结构
type readOnly struct {
    // m 是只读map的实际存储
    m       map[any]*entry
    // amended 为true表示dirty map包含一些不在m中的键
    amended bool
}

关键设计机制:

  1. 不可变性:readOnly结构一旦创建就不会被修改
  2. amended标志:指示dirty map是否包含额外的键
  3. 原子替换:整个readOnly结构通过原子操作替换

entry 条目结构

每个Map条目都通过entry结构管理:

// entry 是Map中对应特定键的槽位
type entry struct {
    // p 指向为条目存储的interface{}值
    //
    // 如果 p == nil,条目已被删除,且要么 m.dirty == nil 要么 m.dirty[key] 是 e
    //
    // 如果 p == expunged,条目已被删除,m.dirty != nil,且条目在 m.dirty 中缺失
    //
    // 否则,条目有效并记录在 m.read.m[key] 中,如果 m.dirty != nil,也在 m.dirty[key] 中
    //
    // 条目可以通过原子替换为nil来删除:当下次创建m.dirty时,
    // 它将原子地用expunged替换nil并保持m.dirty[key]未设置
    //
    // 条目的关联值可以通过原子替换来更新,前提是p != expunged
    // 如果p == expunged,条目的关联值只能在首先设置m.dirty[key] = e后更新,
    // 以便使用dirty map的查找找到条目
    p atomic.Pointer[any]
}

entry状态管理的精妙设计:

  1. 三种状态

    • p != nil && p != expunged:正常状态,包含有效值
    • p == nil:已删除,但可能在dirty map中
    • p == expunged:已删除且不在dirty map中
  2. 状态转换:通过原子操作实现无锁状态管理

  3. expunged标记:特殊的删除标记,用于优化内存使用

// expunged 是一个任意指针,标记已从dirty map中删除的条目
var expunged = new(any)

3. 核心操作流程分析

Load() 读取操作路径

Load()操作是sync.Map的核心,体现了读优化的设计哲学:

// go/src/sync/map.go
func (m *Map) Load(key any) (value any, ok bool) {
    // 1. 首先尝试从read map读取(无锁快速路径)
    read := m.loadReadOnly()
    e, ok := read.m[key]
    
    // 2. 如果read map中没有找到,且dirty map可能包含额外的键
    if !ok && read.amended {
        m.mu.Lock()  // 需要加锁访问dirty map
        
        // 3. 双重检查:避免在等待锁期间dirty map被提升的虚假miss
        read = m.loadReadOnly()
        e, ok = read.m[key]
        
        if !ok && read.amended {
            // 4. 从dirty map中查找
            e, ok = m.dirty[key]
            // 5. 无论是否找到,都记录一次miss
            // 这个键将走慢路径直到dirty map被提升为read map
            m.missLocked()
        }
        m.mu.Unlock()
    }
    
    // 6. 如果没有找到条目,返回零值
    if !ok {
        return nil, false
    }
    
    // 7. 从条目中加载实际值
    return e.load()
}

Load操作的优化策略:

  1. 快速路径优化:大多数读取操作无需加锁
  2. 双重检查模式:避免不必要的miss计数
  3. 渐进式同步:通过miss计数触发Map切换

entry.load()的实现:

func (e *entry) load() (value any, ok bool) {
    p := e.p.Load()  // 原子加载指针
    if p == nil || p == expunged {
        return nil, false  // 条目已被删除
    }
    return *p, true  // 返回实际值
}

Load()操作详细流程图:

flowchart TD
    A["开始: m.Load(key)调用"] --> B["read = m.loadReadOnly()无锁读取"]
    B --> C{"read.m[key]存在?"}
    C -->|是| D["e = read.m[key]"]
    C -->|否| E{"read.amended?"}
    E -->|否| F["return nil, false"]
    E -->|是| G["m.mu.Lock()获取锁"]
    G --> H["read = m.loadReadOnly()双重检查"]
    H --> I{"read.m[key]存在?"}
    I -->|是| J["e = read.m[key]"]
    I -->|否| K{"read.amended?"}
    K -->|否| L["m.mu.Unlock()"]
    K -->|是| M["e, ok = m.dirty[key]"]
    M --> N["m.missLocked()记录miss"]
    N --> O["m.mu.Unlock()"]
    J --> O
    L --> F
    O --> P{"ok?"}
    D --> P
    P -->|否| F
    P -->|是| Q["return e.load()"]
    
    style A fill:#e1f5fe
    style F fill:#ffcdd2
    style Q fill:#c8e6c9
    style B fill:#fff3e0
    style G fill:#ffccbc

missLocked() 源码详解

missLocked()是sync.Map中的核心方法,负责管理miss计数和触发Map提升:

// go/src/sync/map.go
func (m *Map) missLocked() {
    m.misses++  // 增加miss计数
    if m.misses < len(m.dirty) {
        return  // 还未达到提升阈值
    }
    // 提升dirty map为read map
    m.read.Store(&readOnly{m: m.dirty})
    m.dirty = nil    // 清空dirty map
    m.misses = 0     // 重置miss计数
}

missLocked()的设计要点:

  1. 阈值策略:当misses >= len(dirty)时触发提升
  2. 原子替换:通过atomic.Store原子地替换read map
  3. 状态重置:提升后清空dirty map并重置计数器
  4. 性能平衡:平衡复制成本和查找性能

miss计数的触发时机:

flowchart LR
    A["Load操作"] --> B{"read map中存在?"}
    B -->|否| C{"read.amended?"}
    C -->|是| D["检查dirty map"]
    D --> E["missLocked()"]
    E --> F{"misses >= len(dirty)?"}
    F -->|是| G["提升dirty为read"]
    F -->|否| H["继续累积miss"]
    
    style E fill:#ffccbc
    style G fill:#c8e6c9
    style H fill:#fff3e0

Store() 存储操作路径

存储操作通过Swap()方法实现,体现了写操作的复杂性:

// Store 为键设置值
func (m *Map) Store(key, value any) {
    _, _ = m.Swap(key, value)
}

// Swap 交换键的值并返回之前的值(如果有)
func (m *Map) Swap(key, value any) (previous any, loaded bool) {
    // 1. 首先尝试在read map中查找(快速路径)
    read := m.loadReadOnly()
    if e, ok := read.m[key]; ok {
        // 2. 如果在read map中找到,尝试无锁交换
        if v, ok := e.trySwap(&value); ok {
            if v == nil {
                return nil, false
            }
            return *v, true
        }
    }

    // 3. 需要加锁处理(慢路径)
    m.mu.Lock()
    read = m.loadReadOnly()
    
    if e, ok := read.m[key]; ok {
        // 4. 在read map中找到条目
        if e.unexpungeLocked() {
            // 条目之前被删除,需要添加到dirty map
            m.dirty[key] = e
        }
        if v := e.swapLocked(&value); v != nil {
            loaded = true
            previous = *v
        }
    } else if e, ok := m.dirty[key]; ok {
        // 5. 在dirty map中找到条目
        if v := e.swapLocked(&value); v != nil {
            loaded = true
            previous = *v
        }
    } else {
        // 6. 新键,需要添加到dirty map
        if !read.amended {
            // 第一次向dirty map添加新键
            // 确保它被分配并标记read-only map为不完整
            m.dirtyLocked()
            m.read.Store(&readOnly{m: read.m, amended: true})
        }
        m.dirty[key] = newEntry(value)
    }
    m.mu.Unlock()
    return previous, loaded
}
// go/src/sync/map.go
func (m *Map) dirtyLocked() {
    if m.dirty != nil {
        return  // dirty map已存在,无需初始化
    }

    read := m.loadReadOnly()
    m.dirty = make(map[any]*entry, len(read.m))
    
    // 复制read map中的有效条目到dirty map
    for k, e := range read.m {
        if !e.tryExpungeLocked() {
            // 条目未被expunged,复制到dirty map
            m.dirty[k] = e
        }
    }
}

Store操作的关键机制:

  1. trySwap无锁尝试:对于已存在的键,先尝试无锁更新
  2. unexpungeLocked恢复:将已删除的条目重新激活
  3. dirtyLocked初始化:按需创建dirty map
  4. amended标志管理:正确维护read/dirty状态

trySwap() 源码详解

trySwap()方法实现了无锁的值交换操作,是Store操作快速路径的核心:

// go/src/sync/map.go
// 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  // 条目已被expunged,无法交换
        }
        // 尝试原子交换值
        if e.p.CompareAndSwap(p, i) {
            return p, true  // 交换成功,返回旧值
        }
        // CAS失败,重试
    }
}

trySwap()的设计要点:

  1. 无锁操作:使用CAS循环实现无锁值交换
  2. expunged检查:确保条目未被标记为expunged
  3. 原子性保证:通过CompareAndSwap确保操作的原子性
  4. 重试机制:CAS失败时自动重试

trySwap的执行流程:

flowchart TD
    A["trySwap(newValue)"] --> B["p = e.p.Load()"]
    B --> C{"p == expunged?"}
    C -->|是| D["return nil, false"]
    C -->|否| E["e.p.CompareAndSwap(p, newValue)"]
    E --> F{"CAS成功?"}
    F -->|是| G["return p, true"]
    F -->|否| B
    
    style A fill:#e1f5fe
    style D fill:#ffcdd2
    style G fill:#c8e6c9
    style E fill:#fff3e0

Store()操作详细流程图:

flowchart TD
    A["开始: m.Store/Swap调用"] --> B["read = m.loadReadOnly无锁读取"]
    B --> C{"read.m[key]存在?"}
    
    C -->|是| D["尝试e.trySwap无锁更新"]
    D --> E{"trySwap成功?"}
    E -->|是| F["返回结果 - 快速路径"]
    E -->|否| G["m.mu.Lock获取锁"]
    
    C -->|否| G
    G --> H["read = m.loadReadOnly双重检查"]
    H --> I{"read.m[key]存在?"}
    
    I -->|是| J["处理read map中的条目"]
    J --> K{"条目被expunged?"}
    K -->|是| L["e.unexpungeLocked恢复"]
    L --> M["m.dirty[key] = e添加到dirty"]
    K -->|否| N["e.swapLocked交换值"]
    M --> N
    
    I -->|否| O{"m.dirty[key]存在?"}
    O -->|是| P["e.swapLocked交换dirty中的值"]
    O -->|否| Q["新键处理流程"]
    
    Q --> R{"read.amended?"}
    R -->|否| S["m.dirtyLocked初始化dirty map"]
    S --> T["设置read.amended = true"]
    R -->|是| U["dirty map已存在"]
    T --> U
    U --> V["m.dirty[key] = newEntry创建新条目"]
    
    N --> W["m.mu.Unlock释放锁"]
    P --> W
    V --> W
    W --> X["返回结果"]
    
    style A fill:#e1f5fe
    style F fill:#c8e6c9
    style X fill:#c8e6c9
    style G fill:#ffccbc
    style S fill:#fff3e0
    style L fill:#ffccbc

Delete() 删除操作路径

删除操作通过LoadAndDelete()实现:

// LoadAndDelete 删除键的值,返回之前的值(如果有)
func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
    // 1. 首先从read map查找
    read := m.loadReadOnly()
    e, ok := read.m[key]
    
    if !ok && read.amended {
        // 2. read map中没有,但dirty map可能有
        m.mu.Lock()
        read = m.loadReadOnly()
        e, ok = read.m[key]
        
        if !ok && read.amended {
            // 3. 从dirty map中查找并删除
            e, ok = m.dirty[key]
            delete(m.dirty, key)  // 直接从dirty map删除
            // 记录miss,无论是否找到
            m.missLocked()
        }
        m.mu.Unlock()
    }
    
    if ok {
        // 4. 删除条目中的值
        return e.delete()
    }
    return nil, false
}

// entry.delete() 原子删除条目值
func (e *entry) delete() (value any, ok bool) {
    for {
        p := e.p.Load()
        if p == nil || p == expunged {
            return nil, false  // 已经被删除
        }
        // 原子地将值设置为nil(标记为已删除)
        if e.p.CompareAndSwap(p, nil) {
            return *p, true
        }
    }
}

删除操作的设计要点:

  1. 延迟删除:read map中的条目不会立即物理删除
  2. 原子标记:通过CAS操作原子地标记删除
  3. dirty清理:dirty map中的条目会被立即删除

4. 完整数据状态模拟

为了更好地理解sync.Map的工作机制,我们通过一个完整的数据状态模拟来展示Load、Store、Delete操作对内部数据结构的影响。

4.1 初始状态

flowchart TB
    subgraph "sync.Map 初始状态"
        subgraph "Read Map (只读层)"
            RM0["map[any]*entry: 空"]
            RA0["amended: false"]
        end
        
        subgraph "Dirty Map (写入层)"
            DM0["nil"]
        end
        
        subgraph "控制状态"
            MC0["misses: 0"]
            MU0["mu: Mutex (未锁定)"]
        end
    end
    
    style RM0 fill:#f5f5f5
    style DM0 fill:#f5f5f5
    style MC0 fill:#e3f2fd

4.2 第一次Store操作:m.Store("key1", "value1")

操作前状态:

read.m = {}, read.amended = false
dirty = nil, misses = 0

操作步骤:

  1. read := m.loadReadOnly() - 获取当前read状态
  2. read.m["key1"] - 在read map中查找,未找到
  3. m.mu.Lock() - 获取锁
  4. !read.amended - true,需要初始化dirty map
  5. m.dirtyLocked() - 创建dirty map
  6. m.read.Store(&readOnly{m: read.m, amended: true}) - 设置amended标志
  7. m.dirty["key1"] = newEntry("value1") - 添加新条目
  8. m.mu.Unlock() - 释放锁

操作后状态:

flowchart TB
    subgraph "sync.Map 状态桶架构"
        subgraph "Read Map (只读层)"
            RM["map[any]*entry: {}"]
            RA["amended: true"]
        end
        
        subgraph "Dirty Map (写入层)"
            DM["map[any]*entry"]
            DK1["key1 → entry1"]
            DE1["entry1.p → &value1"]
            DK1 --> DE1
        end
        
        subgraph "控制状态"
            MC["misses: 0"]
            MU["mu: Mutex"]
        end
        
        RM -.->|"amended=true"| DM
        MC -.->|"计数触发"| RM
    end
    
    style RM fill:#e3f2fd
    style DM fill:#fff3e0
    style MC fill:#ffccbc

4.3 第二次Store操作:m.Store("key2", "value2")

操作前状态:

read.m = {}, read.amended = true
dirty = {"key1": entry{p: &"value1"}}, misses = 0

操作步骤:

  1. 在read map中查找"key2",未找到
  2. 获取锁
  3. read.amended - true,dirty map已存在
  4. m.dirty["key2"] = newEntry("value2") - 直接添加到dirty map
  5. 释放锁

操作后状态:

flowchart TB
    subgraph "sync.Map 双键状态"
        subgraph "Read Map (只读层)"
            RM3["map[any]*entry: 空"]
            RA3["amended: true"]
        end
        
        subgraph "Dirty Map (写入层)"
            DM3["map[any]*entry"]
            DK31["key1 → entry1"]
            DK32["key2 → entry2"]
            DE31["entry1.p → &value1"]
            DE32["entry2.p → &value2"]
            
            DM3 --> DK31
            DM3 --> DK32
            DK31 --> DE31
            DK32 --> DE32
        end
        
        subgraph "控制状态"
            MC3["misses: 0"]
            MU3["mu: Mutex"]
        end
        
        RA3 -.->|"指示dirty有额外键"| DM3
    end
    
    style RM3 fill:#e3f2fd
    style DM3 fill:#fff3e0
    style MC3 fill:#ffccbc
    style DE31 fill:#c8e6c9
    style DE32 fill:#c8e6c9

4.4 Load操作触发miss:m.Load("key1")

操作前状态:

read.m = {}, read.amended = true
dirty = {"key1": entry, "key2": entry}, misses = 0

操作步骤:

  1. read := m.loadReadOnly() - 获取read状态
  2. read.m["key1"] - 在read map中查找,未找到
  3. read.amended - true,需要检查dirty map
  4. m.mu.Lock() - 获取锁
  5. 双重检查read map,仍未找到
  6. e, ok = m.dirty["key1"] - 在dirty map中找到
  7. m.missLocked() - 记录miss,misses++
  8. m.mu.Unlock() - 释放锁
  9. return e.load() - 返回"value1"

操作后状态:

flowchart TB
    subgraph "Load操作miss状态变化"
        subgraph "操作前"
            A1["misses: 0"]
            A2["read map: 空"]
            A3["dirty map: {key1, key2}"]
        end
        
        subgraph "Load(key1)执行过程"
            B1["1. 检查read map"]
            B2["2. read map未找到"]
            B3["3. 检查amended=true"]
            B4["4. 加锁检查dirty map"]
            B5["5. dirty map找到key1"]
            B6["6. missLocked() misses++"]
        end
        
        subgraph "操作后"
            C1["misses: 1"]
            C2["read map: 空"]
            C3["dirty map: {key1, key2}"]
            C4["返回: value1"]
        end
        
        A1 --> B1
        A2 --> B1
        A3 --> B4
        B1 --> B2
        B2 --> B3
        B3 --> B4
        B4 --> B5
        B5 --> B6
        B6 --> C1
        B6 --> C4
    end
    
    style B6 fill:#ffccbc
    style C1 fill:#fff3e0
    style C4 fill:#c8e6c9

4.5 多次Load触发Map提升

假设我们继续Load操作,当misses >= len(dirty)时,会触发Map提升:

触发条件:

misses = 2, len(dirty) = 2

missLocked()执行Map提升:

func (m *Map) missLocked() {
    m.misses++
    if m.misses < len(m.dirty) {
        return  // 还未达到提升条件
    }
    // 提升dirty map为read map
    m.read.Store(&readOnly{m: m.dirty})
    m.dirty = nil
    m.misses = 0
}

提升后状态:

flowchart TB
    subgraph "Map提升触发过程"
        subgraph "提升前状态"
            P1["read.m: 空"]
            P2["read.amended: true"]
            P3["dirty: {key1, key2}"]
            P4["misses: 2 (≥ len(dirty))"]
        end
        
        subgraph "提升操作"
            O1["1. 检查 misses ≥ len(dirty)"]
            O2["2. read.m = dirty"]
            O3["3. read.amended = false"]
            O4["4. dirty = nil"]
            O5["5. misses = 0"]
        end
        
        subgraph "提升后状态"
            A1["read.m: {key1, key2}"]
            A2["read.amended: false"]
            A3["dirty: nil"]
            A4["misses: 0"]
        end
        
        P4 --> O1
        O1 -->|"条件满足"| O2
        P3 --> O2
        O2 --> O3
        O3 --> O4
        O4 --> O5
        O2 --> A1
        O3 --> A2
        O4 --> A3
        O5 --> A4
    end
    
    style O1 fill:#ffccbc
    style O2 fill:#fff3e0
    style A1 fill:#c8e6c9
    style A2 fill:#c8e6c9

提升后的最终状态架构:

flowchart TB
    subgraph "sync.Map 提升后状态"
        subgraph "Read Map (只读层)"
            RM5["map[any]*entry"]
            RK51["key1 → entry1"]
            RK52["key2 → entry2"]
            RE51["entry1.p → &value1"]
            RE52["entry2.p → &value2"]
            RA5["amended: false"]
            
            RM5 --> RK51
            RM5 --> RK52
            RK51 --> RE51
            RK52 --> RE52
        end
        
        subgraph "Dirty Map (写入层)"
            DM5["nil"]
        end
        
        subgraph "控制状态"
            MC5["misses: 0"]
            MU5["mu: Mutex"]
        end
    end
    
    style RM5 fill:#c8e6c9
    style DM5 fill:#f5f5f5
    style MC5 fill:#e3f2fd
    style RE51 fill:#a5d6a7
    style RE52 fill:#a5d6a7
flowchart TB
    subgraph "Map提升后的状态桶架构"
        subgraph "Read Map (快速访问层)"
            RM2["map[any]*entry"]
            RK1["key1 → entry1"]
            RK2["key2 → entry2"]
            RE1["entry1.p → &value1"]
            RE2["entry2.p → &value2"]
            RA2["amended: false"]
            
            RM2 --> RK1
            RM2 --> RK2
            RK1 --> RE1
            RK2 --> RE2
        end
        
        subgraph "Dirty Map (空闲状态)"
            DN["nil"]
        end
        
        subgraph "控制状态"
            M2["misses: 0"]
            MU2["mu: Mutex"]
        end
        
        RA2 -.->|"无额外键"| DN
    end
    
    style RM2 fill:#c8e6c9
    style DN fill:#f5f5f5
    style M2 fill:#fff3e0

4.6 提升后的Load操作(快速路径)

操作:m.Load("key1")

操作步骤:

  1. read := m.loadReadOnly() - 获取read状态
  2. e, ok := read.m["key1"] - 在read map中找到,ok=true
  3. return e.load() - 直接返回,无需加锁

性能优势:

  • 无锁操作
  • O(1)时间复杂度
  • 无内存分配

4.7 Delete操作:m.Delete("key1")

操作前状态:

read.m = {"key1": entry1, "key2": entry2}, read.amended = false
dirty = nil, misses = 0

操作步骤:

  1. 在read map中找到"key1"对应的entry
  2. e.delete() - 原子地将entry.p设置为nil
  3. 条目在read map中保留,但值被标记为已删除

操作后状态:

flowchart TB
    subgraph "Delete操作状态变化"
        subgraph "删除前状态"
            D1["read.m: {key1, key2}"]
            D2["key1.p → &value1"]
            D3["key2.p → &value2"]
            D4["dirty: nil"]
        end
        
        subgraph "Delete(key1)执行"
            E1["1. 查找read.m[key1]"]
            E2["2. 找到entry1"]
            E3["3. entry1.delete()"]
            E4["4. 原子设置p=nil"]
        end
        
        subgraph "删除后状态"
            F1["read.m: {key1, key2}"]
            F2["key1.p → nil (已删除)"]
            F3["key2.p → &value2"]
            F4["dirty: nil"]
        end
        
        D1 --> E1
        D2 --> E2
        E1 --> E2
        E2 --> E3
        E3 --> E4
        E4 --> F2
        D3 --> F3
        D4 --> F4
    end
    
    style E3 fill:#ffccbc
    style E4 fill:#fff3e0
    style F2 fill:#ffcdd2
    style F3 fill:#c8e6c9

4.8 read map复制到dirty map的状态模拟

为了更好地理解dirtyLocked()的工作机制,我们通过一个完整的状态模拟来展示read map复制到dirty map的过程。

场景设置: 假设当前sync.Map处于提升后的状态,包含多个键值对,其中一些已被删除:

初始状态(提升后):

read.m = {
    "key1": entry{p: &"value1"},     // 正常条目
    "key2": entry{p: nil},           // 已删除条目
    "key3": entry{p: &"value3"},     // 正常条目
    "key4": entry{p: expunged}       // 已清除条目
}
read.amended = false
dirty = nil
misses = 0

触发dirtyLocked()的操作: 当执行m.Store("key5", "value5")时,由于是新键且dirty为nil,会触发dirtyLocked()初始化。

复制前后的架构对比:

flowchart LR
    subgraph "复制前状态"
        subgraph "Read Map"
            R1["key1→entry1(valid)"]
            R2["key2→entry2(deleted)"]
            R3["key3→entry3(valid)"]
            R4["key4→entry4(expunged)"]
        end
        
        subgraph "Dirty Map"
            D0["nil"]
        end
    end
    
    subgraph "复制后状态"
        subgraph "Read Map (不变)"
            R1B["key1→entry1(valid)"]
            R2B["key2→entry2(deleted)"]
            R3B["key3→entry3(valid)"]
            R4B["key4→entry4(expunged)"]
        end
        
        subgraph "Dirty Map (新建)"
            D1["key1→entry1"]
            D2["key2→entry2"]
            D3["key3→entry3"]
            D4["key4被跳过"]
        end
        
        R1B -.->|"共享引用"| D1
        R2B -.->|"共享引用"| D2
        R3B -.->|"共享引用"| D3
    end
    
    style R4 fill:#ffcdd2
    style D0 fill:#f5f5f5
    style D1 fill:#c8e6c9
    style D2 fill:#ffccbc
    style D3 fill:#c8e6c9
    style D4 fill:#ffcdd2

这个完整的模拟展示了:

  1. 双Map架构的协作:read map负责快速读取,dirty map负责写入
  2. 渐进式同步机制:通过miss计数智能地触发Map切换
  3. 无锁优化的效果:提升后的读操作完全无锁
  4. 删除操作的延迟性:避免频繁的Map重建

5. sync.Map 正确用法指南

5.1 基本操作示例

package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map
    
    // 存储键值对
    m.Store("name", "Alice")
    m.Store("age", 30)
    m.Store("city", "Beijing")
    
    // 读取值
    if value, ok := m.Load("name"); ok {
        fmt.Printf("Name: %v\n", value)
    }
    
    // 读取或存储(如果不存在则存储)
    actual, loaded := m.LoadOrStore("country", "China")
    if loaded {
        fmt.Printf("Country already exists: %v\n", actual)
    } else {
        fmt.Printf("Country stored: %v\n", actual)
    }
    
    // 交换值
    if previous, loaded := m.Swap("age", 31); loaded {
        fmt.Printf("Previous age: %v, New age: 31\n", previous)
    }
    
    // 删除并返回值
    if value, loaded := m.LoadAndDelete("city"); loaded {
        fmt.Printf("Deleted city: %v\n", value)
    }
    
    // 遍历所有键值对
    m.Range(func(key, value any) bool {
        fmt.Printf("%v: %v\n", key, value)
        return true // 继续遍历
    })
}

5.2 并发安全示例

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var m sync.Map
    var wg sync.WaitGroup
    
    // 启动多个goroutine并发写入
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key_%d", id)
            value := fmt.Sprintf("value_%d", id)
            m.Store(key, value)
            fmt.Printf("Goroutine %d stored %s=%s\n", id, key, value)
        }(i)
    }
    
    // 启动多个goroutine并发读取
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            time.Sleep(10 * time.Millisecond) // 稍微延迟确保有数据可读
            
            m.Range(func(key, value any) bool {
                fmt.Printf("Reader %d found %v=%v\n", id, key, value)
                return true
            })
        }(i)
    }
    
    wg.Wait()
    
    // 最终状态
    fmt.Println("\nFinal state:")
    m.Range(func(key, value any) bool {
        fmt.Printf("%v: %v\n", key, value)
        return true
    })
}

5.3 性能优化最佳实践

package main

import (
    "fmt"
    "sync"
    "time"
)

// 缓存示例:适合读多写少的场景
type Cache struct {
    data sync.Map
}

func (c *Cache) Get(key string) (interface{}, bool) {
    return c.data.Load(key)
}

func (c *Cache) Set(key string, value interface{}) {
    c.data.Store(key, value)
}

func (c *Cache) GetOrCompute(key string, compute func() interface{}) interface{} {
    if value, ok := c.data.Load(key); ok {
        return value
    }
    
    // 计算新值
    newValue := compute()
    
    // 使用LoadOrStore避免重复计算
    actual, _ := c.data.LoadOrStore(key, newValue)
    return actual
}

func main() {
    cache := &Cache{}
    
    // 模拟计算密集型操作
    expensiveCompute := func() interface{} {
        time.Sleep(100 * time.Millisecond) // 模拟耗时计算
        return "computed_value"
    }
    
    var wg sync.WaitGroup
    
    // 多个goroutine同时请求相同的key
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            start := time.Now()
            value := cache.GetOrCompute("expensive_key", expensiveCompute)
            duration := time.Since(start)
            fmt.Printf("Goroutine %d got value: %v, took: %v\n", id, value, duration)
        }(i)
    }
    
    wg.Wait()
}

5.4 适用场景与注意事项

适用场景:

  1. 读多写少:大量并发读取,少量写入操作
  2. 缓存系统:Web应用中的数据缓存
  3. 配置管理:应用配置的动态更新和读取
  4. 连接池:数据库连接、HTTP客户端等资源池管理

总结

通过本文的深入分析,我们不仅掌握了sync.Map的使用方法,更重要的是理解了其背后的设计思想和技术原理。这些知识将帮助我们在面对复杂的并发编程挑战时,能够做出更明智的技术选择,构建出更加高效、稳定的并发系统。