面试题答案
一键面试sync.Once结构体主要字段及其作用
sync.Once
结构体在Go语言中的定义如下:
type Once struct {
done uint32
m Mutex
}
done
(uint32
):- 用于标记初始化是否已经完成。它是一个32位无符号整数,当它的值为0时,表示初始化尚未进行;非0值表示初始化已经完成。通过使用原子操作对其进行读写,以确保在多线程环境下的正确性。
m
(Mutex
):- 互斥锁,用于保护对
done
字段的读写操作,以及在初始化未完成时,保护初始化函数的执行。当有多个 goroutine 同时尝试初始化时,通过互斥锁来保证只有一个 goroutine 能够进入初始化逻辑,避免重复初始化。
- 互斥锁,用于保护对
Do方法的具体执行逻辑
Do
方法的定义如下:
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
- 快速路径检查:
Do
方法首先使用atomic.LoadUint32
原子操作读取done
字段的值。如果done
不为0,说明初始化已经完成,直接返回,不再执行初始化函数f
。这是一个快速路径,避免了在已经初始化完成的情况下获取锁和重复执行初始化函数的开销。
- 慢速路径执行:
- 如果
done
为0,说明初始化尚未完成,调用doSlow
方法。 - 在
doSlow
方法中,首先获取互斥锁o.m
,这样可以确保只有一个 goroutine 能够进入初始化逻辑。 - 再次检查
done
是否为0,这是因为在获取锁的过程中,可能已经有其他 goroutine 完成了初始化。如果done
仍然为0,则执行初始化函数f
。 - 在执行
f
之前,使用defer
语句确保在f
执行完毕后,通过atomic.StoreUint32
原子操作将done
设置为1,表示初始化已经完成。然后释放互斥锁。
- 如果
通过这种方式,sync.Once
实现了懒汉式初始化模式,在多线程环境下保证初始化函数只被执行一次。