고루틴 관리
고루틴은 가벼우나 무료가 아닙니다. 최소한으로 메모리와 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를 사용해야 한다는 것입니다.