본문 바로가기
Study/Go

[Go] 고루틴(GoRoutine) 심화(2) - 클로저 함수, 메모리

by _royJang 2022. 4. 28.

클로저 함수를 통해 고루틴을 생성한다면 어떤 일이 생길까? 그리고 이 부분에서 무엇을 배울 수 있을까?

책에서는 짧게 설명하고 넘어가는 부분이다. 하지만 나는 쉽게 이해할 수 없었고 그 이유를 찾기 위해 노력했다. 그리고 그 이유는 Go 언어가 메모리를 어떻게 사용하는지 모르기 때문인 것을 알 수 있었다.

그렇기에 이번 포스팅에서는 클로저 함수를 통해 고루틴을 생성하는 것에 대한 예제와 결과를 통해 Go에서 메모리를 어떻게 사용하는지 까지 정리해 보려 한다.

우선 짧은 예제를 살펴보자.

예제 코드

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    val := "memory 1"
    wg.Add(1)
    go func() {
        defer wg.Done()
        val = "memory 2"
    }()
    wg.Wait()
    fmt.Println(val)
}

기본적으로 클로저 함수는 함수 외부의 변수를 가져다 사용할 수 있고 Go 역시 다르지 않다. 그렇기에 결과는

결과

memory 2

Go는 외부 변수의 값을 복사해서 사용하는 것이 아니라 변수의 주소를 참조하는 것임을 예상할 수 있다.

그렇다면 슬라이스를 사용해 보자.

예제 코드

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for _, num := range []int{1, 2, 3} {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Printf("%d ", num)
        }()
    }
    wg.Wait()
}

외부 변수를 사용하는 클로저에 단지 슬라이스가 할당된 변수를 사용했을 뿐이다.
값은 1 2 3을 기대할 수 있다.(내 기준..)

결과

3 3 3

?? 이해하는데 애먹었다. 고루틴이 실행되는 순서에 따라 1 2 3의 순서가 정해져 있지는 않다고 생각했지만 이런 결과를 불러올 줄은 몰랐다. 그렇다면 왜 이런 결과를 불러오는 것일까? 이 부분에서 Go가 고루틴을 사용할 때 메모리를 관리하는 법을 예측할 수 있다. 첫 예제를 통해 변수의 주소를 참조하는 것을 확인했고 두번째 예제를 통해 이러한 이유 때문에 우리의 생각대로 변수가 할당되는 것이 아님을 알 수 있다.
만일 합류지점을 정해주지 않았다면 일반적으로 for문의 고루틴이 시작도 하기전에 메인 고루틴이 종료될 것이고 이는 슬라이스 값이 범위를 벗어났음을 의미한다.
하지만 Go는 여전히 값이 참조되고 있음을 알고 있는 상황이고 이런 상황에서 Stack 메모리 영역의 값을 Heap 메모리 영역으로 넘겨 값을 참조할 수 있도록 해 준다.
앞서 말했듯 이 상황에선 일반적으로 for문이 끝나고 고루틴이 실행되기에 Heap 메모리 영역에 슬라이스의 마지막 값인 3이 저장돼 있기 때문에 위와 같은 결과를 얻을 수 있는 것이다.

그럼 예상한 결과가 나오게 하려면 어떡해야 할까?

예제 코드

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for _, num := range []int{1, 2, 3} {
        wg.Add(1)
        go func(num int) {
            defer wg.Done()
            fmt.Printf("%d ", num)
        }(num)
    }
    wg.Wait()
}

참조에 의한 값 전달이 아니라 직접 복사해 주면 된다.

결과

1 2 3

예상한 결과가 나오는 것을 확인할 수 있다.


O'Reilly에서 출판한 Concurrency in Go를 참고하여 작성한 글입니다.

본인의 공부를 위한 포스팅이며 틀린 부분이 있을 수 있습니다. 만일 틀린 부분을 발견한다면 말씀해 주시면 감사하겠습니다.