面试题答案
一键面试sync.Once 实现单例模式原理
- sync.Once 结构体:
- 在 Go 语言中,
sync.Once
结构体定义如下:
type Once struct { done uint32 m Mutex }
done
字段:- 类型为
uint32
,用于标记初始化是否已经完成。当done
为 0 时,表示尚未初始化;当done
为非 0 值时,表示已经完成初始化。这个字段使用原子操作来保证在多 goroutine 环境下的正确读写。
- 类型为
m
字段:- 类型为
Mutex
,即互斥锁。它的作用是在初始化操作时进行同步,避免多个 goroutine 同时执行初始化代码。
- 类型为
- 在 Go 语言中,
- 初始化过程:
sync.Once
主要通过Do
方法来实现单例初始化,Do
方法的定义如下:
func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 0 { o.doSlow(f) } }
- 首先通过
atomic.LoadUint32
原子操作检查done
字段,如果done
已经非 0,说明初始化已经完成,直接返回,不再执行初始化函数f
。 - 如果
done
为 0,调用doSlow
方法:
func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } }
- 在
doSlow
方法中,首先获取互斥锁m
,这样可以保证同一时间只有一个 goroutine 能进入后续的初始化检查和执行。 - 再次检查
done
字段(双重检查锁定机制),这是因为在获取锁之前可能有其他 goroutine 已经完成了初始化。 - 如果
done
仍为 0,执行初始化函数f
,并在初始化完成后通过atomic.StoreUint32
原子操作将done
设置为 1,表示初始化完成。
- 多 goroutine 环境下单例唯一性保证:
- 原子操作:
done
字段使用原子操作atomic.LoadUint32
和atomic.StoreUint32
来读取和设置,这样可以保证在多 goroutine 并发环境下,对done
字段的读写操作是线程安全的,不会出现数据竞争导致的错误判断。 - 互斥锁:在初始化过程中,通过互斥锁
m
来保证同一时间只有一个 goroutine 能够执行初始化函数f
。即使多个 goroutine 同时进入Do
方法且都发现done
为 0,由于互斥锁的存在,只有一个 goroutine 能真正执行f
,其他 goroutine 在获取锁时会被阻塞,待初始化完成后,其他 goroutine 再次检查done
字段就会发现已经初始化完成,从而直接返回,确保了单例的唯一性。
- 原子操作: