더북(TheBook)

이 프로그램은 루프를 1000번 돌며 고루틴 1000개를 수행한다. 각 고루틴에서는 counter 타입 변수 c의 값을 increment() 메서드로 1씩 증가시킨다. increment() 메서드는 총 1000번 수행되므로 프로그램 실행 결과로 1000이 출력되어야 한다. 하지만 실제로 프로그램을 실행해 보면 1000보다 작은 값이 출력된다. 이는 여러 고루틴이 카운터 c의 내부 필드 i의 값을 동시에 수정하려고 해서 경쟁상태가 만들어졌고, 이로 인해 정확한 결과를 얻지 못했기 때문이다.

Note 예제에서 사용된 Go 기본 라이브러리 함수

runtime.GOMAXPROCS(): 현재 프로그램에서 사용할 CPU의 최대 수

runtime.NumCPU(): CPU 코어 수

정확한 결과를 얻으려면 공유 데이터인 카운터 c의 내부 필드 값 i를 변경하는 부분을 뮤텍스로 보호해주어야 한다.


package main
 
import (
    "fmt"
    "runtime"
    "sync"
)
 
type counter struct {
    i int64
    mu sync.Mutex // 공유 데이터 i를 보호하기 위한 뮤텍스
}
 
// counter 값을 1씩 증가시킴
func (c *counter) increment() {
    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의 값 출력
}

실행 결과

1000

counter 구조체를 정의할 때 내부 필드 값 i를 보호하기 위해 sync.Mutex를 내부 필드 mu로 정의했다. increment() 메서드에서는 c.mu.Lock()c.mu.Unlock()을 작성하여 i 값을 변경하는 부분을 뮤텍스로 보호해주었다. c.display()로 처리 결과를 확인해 보자. increment() 메서드를 수행한 수만큼 값이 정확하게 증가한 것을 확인할 수 있다.

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