Golang中sync/map.go文件里"for"关键字的用法

Golang中sync/map.go文件里"for"关键字的用法 在 https://github.com/golang/go/blob/master/src/sync/map.go 中的函数:tryStore:170, tryLoadOrStore:240, delete:297 都包含 for {} 循环,有人能解释一下这样做的原因吗?

3 回复

非常感谢

更多关于Golang中sync/map.go文件里"for"关键字的用法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好 @xingkong

我猜你的问题是关于为什么这些循环没有条件,对吗?

循环体内包含的 if 条件取代了循环条件。我想这主要是为了可读性。如果一个循环有两个或三个退出条件,将它们作为循环内独立的 if 语句,可能比构建一个冗长、复杂的 for 条件表达式更具可读性。

sync/map.go 中,for {} 循环用于实现无锁(lock-free)或乐观锁(optimistic concurrency)的并发控制模式。这种模式允许在并发环境下安全地操作共享数据,而无需使用传统的互斥锁(mutex)来阻塞其他 goroutine。

具体来说,for {} 循环通常与 atomic 包中的原子操作(如 CompareAndSwapLoadStore)结合使用,通过循环重试机制来处理并发冲突。当多个 goroutine 同时尝试修改同一数据时,只有一个能成功执行原子操作,其他失败的 goroutine 会重新循环并重试,直到成功或达到特定条件。

示例分析:tryStore 函数(第170行)

func (m *Map) tryStore(key, value interface{}) bool {
    read, _ := m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok && e.tryStore(&value) {
        return true
    }
    return false
}

tryStore 中,for {} 循环实际上位于 e.tryStore 方法内部(在 entry 类型中定义)。以下是 entry.tryStore 的简化逻辑:

func (e *entry) tryStore(i *interface{}) bool {
    for {
        p := atomic.LoadPointer(&e.p)
        if p == expunged {
            return false
        }
        if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
            return true
        }
    }
}

解释

  • 使用 atomic.LoadPointer 原子加载当前指针值。
  • 如果指针标记为 expunged(表示条目已删除),则返回失败。
  • 使用 atomic.CompareAndSwapPointer 尝试原子地交换指针值。如果失败(说明其他 goroutine 已修改该值),则循环重试。

示例分析:tryLoadOrStore 函数(第240行)

func (e *entry) tryLoadOrStore(i interface{}) (actual interface{}, loaded, ok bool) {
    p := atomic.LoadPointer(&e.p)
    if p == expunged {
        return nil, false, false
    }
    if p != nil {
        return *(*interface{})(p), true, true
    }
    // 尝试存储新值
    ic := i
    for {
        if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) {
            return i, false, true
        }
        p = atomic.LoadPointer(&e.p)
        if p == expunged {
            return nil, false, false
        }
        if p != nil {
            return *(*interface{})(p), true, true
        }
    }
}

解释

  • 首先检查条目状态,如果已删除或已有值,则直接返回。
  • 进入 for {} 循环,尝试使用 CompareAndSwapPointer 将值从 nil 设置为新值。
  • 如果失败(其他 goroutine 已修改),重新加载指针并检查状态,循环重试直到成功或遇到终止条件。

示例分析:delete 函数(第297行)

func (e *entry) delete() (value interface{}, ok bool) {
    for {
        p := atomic.LoadPointer(&e.p)
        if p == nil || p == expunged {
            return nil, false
        }
        if atomic.CompareAndSwapPointer(&e.p, p, nil) {
            return *(*interface{})(p), true
        }
    }
}

解释

  • 循环加载当前指针值,如果为 nil 或已标记删除,则返回失败。
  • 尝试使用 CompareAndSwapPointer 将指针原子地设置为 nil(表示删除)。
  • 如果失败(其他 goroutine 同时修改),则循环重试。

总结原因:

  1. 无锁并发:通过原子操作和循环重试,避免使用互斥锁带来的阻塞和上下文切换开销。
  2. 乐观并发控制:假设冲突较少,先尝试操作,失败时重试,适用于高并发读多写少的场景。
  3. 保证原子性CompareAndSwap 等原子操作确保在并发修改时的数据一致性。
  4. 避免ABA问题:在 sync.Map 的实现中,通过 expunged 标记处理指针重用问题。

这种模式在并发编程中常见,特别是在实现高性能并发数据结构时。sync.Map 通过结合 readOnly 映射和 dirty 映射的分段设计,进一步优化了这种无锁循环的性能。

回到顶部