Skip to content

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
}

내부 구조는 매우 간단합니다.

  1. done, 실행 여부를 나타내는 원자값입니다.
  2. 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()
	}
}

코드는 전체적으로 매우 간단하며 플로우는 다음과 같습니다.

  1. 원자값을 직접 로드하며 이미 실행되었으면 바로 반환합니다.
  2. 실행되지 않았으면 락을 얻으려고 시도하며 여기서는 여러 코루틴이 뮤텍스를 경쟁할 수 있습니다.
  3. 승자가 성공적으로 락을 얻은 후에도 done 값을 다시 한 번 판단해야 합니다. 락을 보유한 후 다른 사람이 이미 락 추가 - 실행 - 락 해제를 완료했을 수 있기 때문입니다. 이때 막 깨어난 것입니다.
  4. 대상 함수를 실행합니다.
  5. done 값을 업데이트합니다.
  6. 락을 해제합니다.

요약

sync.Once 는 매우 간결하고 효율적인 동기화 도구로, 동시성 환경에서 특정 작업이 한 번만 실행되도록 보장하여 반복 작업이나 리소스 낭비를 피합니다.

Golang by www.golangdev.cn edit