더북(TheBook)

5.3.3 sync.Once

특정 함수를 한 번만 수행해야 할 때 sync.Once를 사용한다.

sync.Once 구조체는 다음 메서드를 제공한다.

func (o *Once) Do(f func())

한 번만 수행해야 하는 함수를 Do() 메서드의 매개변수로 전달하여 실행하면 여러 고루틴에서 실행한다 해도 해당 함수는 한 번만 수행된다.

다음 코드에서는 카운터의 내부 필드 값 i의 초기화 작업을 Once 구조체의 Do() 메서드로 지정했다.


package main
 
import (
    "fmt"
    "runtime"
    "sync"
)
 
const initialValue = -500
 
type counter struct {
    i int64
    mu sync.Mutex  // 공유 데이터 i를 보호하기 위한 뮤텍스
    once sync.Once // 한 번만 수행할 함수를 지정하기 위한 Once 구조체
}
 
// counter 값을 1씩 증가시킴
func (c *counter) increment() {
    // i 값 초기화 작업은 한 번만 수행되도록 once의 Do() 메서드로 실행
    c.once.Do(func() {
        c.i = initialValue
    })
     
    c.mu.Lock()   // i 값을 변경하는 부분(임계 영역)을 뮤텍스로 잠금
    c.i += 1      // 공유 데이터 변경
    c.mu.Unlock() // i 값을 변경 완료한 후 뮤텍스 잠금 해제
}
 
// counter의 값을 출력
func (c *counter) display() {
    fmt.Println(c.i)
}
 
func main() {
    // 모든 CPU를 사용하게 함
    runtime.GOMAXPROCS(runtime.NumCPU())
     
    c := counter{i: 0}          // 카운터 생성
    done := make(chan struct{}) // 완료 신호 수신용 채널
   
    // c.increment()를 실행하는 고루틴 1000개 실행
    for i := 0; i < 1000; i++ {
        go func() {
            c.increment()      // 카운터 값을 1 증가시킴
            done <- struct{}{} // done 채널에 완료 신호 전송
        }()
    }
     
    // 모든 고루틴이 완료될 때까지 대기
    for i := 0; i < 1000; i++ {
        <-done
    }
     
    c.display() // c의 값 출력
}

실행 결과

500

수행 결과를 보면 c.increment()는 1000번 수행됐지만, 카운터 내부 필드 값 i의 초기화 작업은 단 한 번만 수행된 것을 확인할 수 있다.

신간 소식 구독하기
뉴스레터에 가입하시고 이메일로 신간 소식을 받아 보세요.