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 {} 循环,有人能解释一下这样做的原因吗?
你好 @xingkong,
我猜你的问题是关于为什么这些循环没有条件,对吗?
循环体内包含的 if 条件取代了循环条件。我想这主要是为了可读性。如果一个循环有两个或三个退出条件,将它们作为循环内独立的 if 语句,可能比构建一个冗长、复杂的 for 条件表达式更具可读性。
在 sync/map.go 中,for {} 循环用于实现无锁(lock-free)或乐观锁(optimistic concurrency)的并发控制模式。这种模式允许在并发环境下安全地操作共享数据,而无需使用传统的互斥锁(mutex)来阻塞其他 goroutine。
具体来说,for {} 循环通常与 atomic 包中的原子操作(如 CompareAndSwap、Load、Store)结合使用,通过循环重试机制来处理并发冲突。当多个 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 同时修改),则循环重试。
总结原因:
- 无锁并发:通过原子操作和循环重试,避免使用互斥锁带来的阻塞和上下文切换开销。
- 乐观并发控制:假设冲突较少,先尝试操作,失败时重试,适用于高并发读多写少的场景。
- 保证原子性:
CompareAndSwap等原子操作确保在并发修改时的数据一致性。 - 避免ABA问题:在
sync.Map的实现中,通过expunged标记处理指针重用问题。
这种模式在并发编程中常见,特别是在实现高性能并发数据结构时。sync.Map 通过结合 readOnly 映射和 dirty 映射的分段设计,进一步优化了这种无锁循环的性能。

