고루틴 관리

고루틴은 가벼우나 무료가 아닙니다. 최소한으로 메모리와 CPU 스케줄링을 위한 스택을 사용합니다. 고루틴을 사용할 때 이러한 비용은 작지만, 제어되지 않는 수명주기로 대량 생성하면 심각한 성능 문제를 야기할 수 있습니다. 관리되지 않는 수명주기를 가진 고루틴은 사용되지 않는 객체가 가비지 컬렉션되는 것을 방해하고 사용되지 않는 리소스를 보유하는 문제를 야기할 수도 있습니다.

따라서 코드에서 고루틴을 누설하지 마십시오. go.uber.org/goleak을 사용하여 고루틴 누설을 테스트하고 고루틴을 생성할 수 있는 패키지 내에서 고루틴 누설을 확인하십시오.

일반적으로 각 고루틴은 다음을 준수해야 합니다:

  • 예측 가능한 종료 시간을 가져야 하거나
  • 고루틴에게 종료해야 함을 신호하는 방법을 가져야 합니다.

두 경우 모두 코드가 블록되고 고루틴이 완료되기를 기다릴 수 있는 방법이 있어야 합니다.

예를 들어:

비권장:

go func() {
  for {
    flush()
    time.Sleep(delay)
  }
}()
// 이 고루틴을 중단할 수 있는 방법이 없습니다. 응용 프로그램이 종료될 때까지 무기한 실행됩니다.

권장:

var (
  stop = make(chan struct{}) // 고루틴에 중지 신호를 보냅니다
  done = make(chan struct{}) // 고루틴이 종료되었음을 알리는 신호
)
go func() {
  defer close(done)
  ticker := time.NewTicker(delay)
  defer ticker.Stop()
  for {
    select {
    case <-ticker.C:
      flush()
    case <-stop:
      return
    }
  }
}()

// ... 다른 코드
close(stop)  // 고루틴을 중지해야 함을 나타냅니다
<-done       // 그리고 그것이 종료될 때까지 기다립니다

// 이 고루틴은 close(stop)로 대기할 수 있고, 그것이 종료될 때까지 기다릴 수 있습니다 <-done.

고루틴 종료를 기다리기

시스템에서 생성된 고루틴이 있을 때, 해당 고루틴이 종료될 때까지 기다릴 수 있는 방법이 있어야 합니다. 이를 달성하기 위한 두 가지 일반적인 방법이 있습니다:

  • sync.WaitGroup을 사용합니다. 여러 고루틴을 기다리는 경우에 이 방법을 사용하십시오.
var wg sync.WaitGroup
for i := 0; i < num; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        // ...
    }()
}
wg.Wait()
  • 다른 chan struct{}을 추가하고, 고루틴이 완료된 후에 이를 닫습니다. 한 고루틴만 있는 경우에는 이 방법을 사용하십시오.
done := make(chan struct{})
go func() {
    defer close(done)
    // ...
}()
// 고루틴이 완료될 때까지 기다리려면:
<-done

init() 함수는 고루틴을 생성해서는 안 됩니다. 또한, init() 사용을 피하십시오 를 참조하십시오.

패키지가 백그라운드 고루틴이 필요한 경우, 해당 패키지는 백그라운드 고루틴의 수명주기를 관리하는 객체를 노출해야 합니다. 이 객체는 백그라운드 고루틴의 종료를 나타내고 기다리기 위한 메서드(Close, Stop, Shutdown 등)를 제공해야 합니다.

비권장 접근 방법:

func init() {
    go doWork()
}
func doWork() {
    for {
        // ...
    }
}
// 사용자가 이 패키지를 내보낼 때, 백그라운드 고루틴이 무조건 생성됩니다. 사용자는 고루틴을 제어하거나 중지시킬 수 없습니다.

권장 접근 방법:

type Worker struct{ /* ... */ }
func NewWorker(...) *Worker {
    w := &Worker{
        stop: make(chan struct{}),
        done: make(chan struct{}),
        // ...
    }
    go w.doWork()
}
func (w *Worker) doWork() {
    defer close(w.done)
    for {
        // ...
        select {
        case <-w.stop:
            return
        }
    }
}
// Shutdown은 worker에게 작업을 중지하도록 지시하고 작업이 완료될 때까지 기다립니다.
func (w *Worker) Shutdown() {
    close(w.stop)
    <-w.done
}

사용자가 요청할 때만 worker를 생성하고, 사용자가 worker가 사용한 리소스를 해제할 수 있도록 worker를 중지하는 방법을 제공해야 합니다.

여기서 주의해야 할 점은 worker가 여러 고루틴을 관리하는 경우 WaitGroup를 사용해야 한다는 것입니다.