[예제로 배우는 Go 프로그래밍] 6. 고루틴, sync 패키지

Melon Coder·2024년 7월 2일

Go

목록 보기
10/14

Go 루틴 (goroutine)

1. Go 루틴

Go에서 go키워드를 사용하여 함수를 호출하면, 런타임 시 새로운 goroutine을 실행한다.

goroutine은 비동기적으로 (asynchronously) 함수를 실행하므로, 여러 코드를 동시에 실행하는데 사용된다.

2. 익명함수 Go 루틴

goroutine은 익명함수 에 대해 사용할 수도 있다.
즉, go 키워드 뒤에 익명함수를 바로 정의하는 것으로, 이는 익명함수를 비동기적으로 실행하게 된다.

func main() {
	// WaitGroup 생성. 2개의 고루틴을 기다림.
	var wait sync.WaitGroup
	wait.Add(2)

	// 익명함수를 사용한 고루틴
	go func() {
		defer wait.Done() // 끝나면 Done() 호출
		fmt.Println("Hello")
	}()

	// 익명함수에 파라미터 전달
	go func(msg string) {
		defer wait.Done() // 끝나면 Done() 호출
		fmt.Println(msg)
	}("Hi")

	wait.Wait() // 고루틴 모두 끝날 때까지 대기
}

예제 코드를 작성하고 살펴보니 sync 키워드를 마주하게 되었다.

먼저 병렬적으로 어떤 프로그램을 설계하고자 할 때는 고려해야 할 사항이 많다.
예를 들면 한 자원에 동시에 접근하는 경우, 혹은 의존성이 있는 작업간의 순서 처리를 명시적으로 제어해야 하는 경우...

바로 이 sync 패키지는 Go에 내장 패키지로써 쉽고 직관적으로 제어할 수 있게 도와준다.

vsCode에서 sync를 타이핑하고 자동완성을 확인해보면, (GO extension 설치 필요) Cond, Locker, Map, Mutex, Once... 그리고 위 코드에 적혀있는 WaitGroup 까지 나오는 것을 확인할 수 있다. (Pool, RWMutex 도 있음.)

그럼 먼저 위 코드에서 쓰였던 sync.WaitGroup에 대해 알아보자.

★ WaitGroup

예시를 통해 설명을 해보겠다.

var globalValue int

func action(i int) {
    globalValue += i
    time.Sleep(1 * time.Second)
}
func main() {
    startTime := time.Now()

    for i := 0; i < 100; i++ {
        action(i)
    }

    delta := time.Now().Sub(startTime)
    fmt.Printf("Result is %d, done in %.3fs.\n", globalValue, delta.Seconds())
}

위 코드를 간단하게 말하자면 반복문(for문)을 통해 0부터 99까지 더하는 코드이다.

0부터 99까지 호출 즉, 100번 호출하고 있고 직렬적으로 처리하기 때문에 약 100초의 시간이 소요된다.

여기서 병렬적으로 처리하기 위해 고루틴을 사용하면? (action 함수 앞에 go 를 붙여 호출)

0초에서 0.001초가 걸렸으나 결과 값이 맞지 않는다.
이유는 무엇일까

그 이유는 고루틴은 백그라운드에서 비동기적으로 실행되는데, 프로그램은 고루틴이 끝날 때까지 기다려주지 않기 때문이다.

위 프로그램에서 100개의 고루틴이 생성되기는 했지만, 모두 실행이 완료되기 전에 프로그램이 종료되었던 것이다.

위와 같은 문제를 바로 sync.WaitGroup을 이용해 제어한다.

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.

WaitGroup은 고루틴이 완료될 때까지 기다립니다. 메인 고루틴은 Add를 호출하여 몇 개의 고루틴을 기다릴 것인지 설정합니다. 각각의 고루틴은 작업이 완료되면 Done을 호출합니다. Wait는 모든 고루틴이 완료될 때까지 프로그램을 블락합니다.

WaitGroup을 통해 코드를 수정하니 4950 (나와야 하는 값)이 나왔다. 하지만...

매번 맞지는 않는다.
왜 이럴까?

레이스 컨디션 (Race Condition)

레이스 컨디션이란 하나의 자원에 여러 프로세스가 동시에 접근하려고 하면서 서로 경쟁하는 상태 이다.

위 예제를 보면 action 함수가 여러 개의 고루틴에서 동시에 실행되며 globalValue에 동시에 접근한다.

globalValue에 접근하는 코드는

globalValue += i

인데 여기서 보면 각각의 연산이 따로따로 실행될 것이다.
그렇기 때문에 2개 이상의 고루틴이 동시에 실행된다면 각각 다른 값을 더하고 있을 것이다.

이 문제를 해결하기 위해서 sync.Mutex가 나온다.

sync.Mutex

mutex는 락을 걸 수 있다.
즉, 여러 쓰레드가 실행되는 환경에서 자원에 접근을 제한하는 동기화 매커니즘이다.

mutex.Lock(), mutex.Unlock() 을 활용하여 사용할 수 있다.

profile
Frontend developer

0개의 댓글