once
sync.Once 는 Go 표준 라이브러리의 동기화 도구로, 동시성 환경에서 특정 함수가 한 번만 실행되도록 보장합니다. 이는 일반적으로 지연 초기화, 전역 리소스 초기화 등의 시나리오에서 사용되며 특정 작업이 여러 코루틴이 동시에 실행하더라도 한 번만 실행되도록 보장합니다.
go
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var once sync.Once
for i := range 10 {
wg.Add(1)
go func() {
defer wg.Done()
once.Do(func() {
fmt.Println(i)
})
}()
}
wg.Wait()
}위 코드에서 총 10 개의 코루틴이 있지만 어쨌든 하나의 코루틴만 once.Do() 의 fmt.Println(i) 를 실행합니다. 어떤 코루틴이 인쇄할지는 어떤 코루틴이 가장 먼저 once.Do() 에 도달하는지에 따라 결정됩니다.
구조
go
type Once struct {
done atomic.Uint32
m Mutex
}내부 구조는 매우 간단합니다.
done, 실행 여부를 나타내는 원자값입니다.m, 뮤텍스로 실행하려는 다른 코루틴을 블록하는 데 사용됩니다.
원리는 실행 전에 먼저 락을 걸고 done 을 업데이트한 후 실행이 완료되면 락을 해제하는 것입니다.
Do
go
func (o *Once) Do(f func()) {
if o.done.Load() == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done.Load() == 0 {
defer o.done.Store(1)
f()
}
}코드는 전체적으로 매우 간단하며 플로우는 다음과 같습니다.
- 원자값을 직접 로드하며 이미 실행되었으면 바로 반환합니다.
- 실행되지 않았으면 락을 얻으려고 시도하며 여기서는 여러 코루틴이 뮤텍스를 경쟁할 수 있습니다.
- 승자가 성공적으로 락을 얻은 후에도
done값을 다시 한 번 판단해야 합니다. 락을 보유한 후 다른 사람이 이미 락 추가 - 실행 - 락 해제를 완료했을 수 있기 때문입니다. 이때 막 깨어난 것입니다. - 대상 함수를 실행합니다.
done값을 업데이트합니다.- 락을 해제합니다.
요약
sync.Once 는 매우 간결하고 효율적인 동기화 도구로, 동시성 환경에서 특정 작업이 한 번만 실행되도록 보장하여 반복 작업이나 리소스 낭비를 피합니다.
