본문 바로가기
Study/Go

[Go] 고루틴(GoRoutine) 심화(5) - 누수 관리

by _royJang 2022. 5. 24.

메모리 누수

비록 고루틴이 다른 쓰레드에 비해 가볍긴 하지만 메모리를 사용하는 것은 당연할 것이다.

그렇기에 사용하진 않지만 살아있는 고루틴은 프로세스의 메모리를 쓸데없이 낭비하게 될 것이다.

이런 현상을 메모리 누수라 한다. 메모리 누수를 관리하는 방법을 살펴보자.

누수 관리

누수가 일어나는 예시

package main

import (
    "fmt"
)

func main() {
    doWork := func(strings <-chan string) <-chan interface{} {
        completed := make(chan interface{})
        go func() {
            defer fmt.Println("doWork existed")
            defer close(complted)
            for s := range strings {
                fmt.Println(s)
            }
        }()
        return completed
    }
    doWork(nil)
    fmt.Println("Done")
}

예제에서는 doWork에 nil 채널을 전달하는 것을 볼 수 있다. nil 채널을 수신하는 것은 대기를 유발하기 때문이다. 그로인해 doWork는 어떠한 일도 하지 않지만 메인 고루틴이 죽을 때 까지 메모리에 doWork 프로세스는 계속해서 메모리를 차지하고 있을 것이다.

이것을 방지하기 위해 사용할 수 있는 하나의 기법은 for-select 를 사용하는 것이다.

누수를 관리할 수 있는 예시

package main

import (
    "fmt"
    "time"
)

func main() {
    doWork := func(done <-chan interface{}, strings <-chan string) <-chan interface{} {
        terminated := make(chan interface{})
        go func() {
            defer fmt.Println("doWork exited.")
            defer close(terminated)
            for {
                select {
                case s := <-strings:
                    fmt.Println(s)
                case <-done:
                    return

                }
            }
        }()

        return terminated
    }
    done := make(chan interface{})
    terminated := doWork(done, nil)

    go func() {
        time.Sleep(1 * time.Second)
        fmt.Println("Canceling doWork goroutine...")
        close(done)
    }()
    <-terminated
    fmt.Println("Done")
}

결과

Canceling doWork goroutine...
doWork exited.
Done

done 채널이 일반적으로 첫 번째 매개 변수를 차지한다고 한다. 코드를 살펴보면 strings 채널에 nil을 전달했지만 고루틴은 성공적으로 마무리 되는 것을 볼 수 있다.