Goroutine Yönetimi

Gorutinler hafiftir, ancak bedava değillerdir: en azından, yığın ve CPU zamanlaması için bellek tüketirler. Gorutinlerin kullanımı için bu maliyetler küçük olsa da, kontrollü bir yaşam döngüsü olmadan büyük miktarlarda üretilmeleri ciddi performans sorunlarına yol açabilir. Yönetilmeyen yaşam döngüsüne sahip gorutinler, kullanılmayan nesnelerin çöp toplamasını engelleyebilir ve kullanılmayan kaynakları elinde tutabilir.

Bu nedenle, kodunuzda gorutin sızıntısı oluşturmayın. Gorutin sızıntılarını test etmek için go.uber.org/goleak kullanarak paket içinde gorutin sızıntısı oluşturabilecek.

Genel olarak, her bir gorutinin şunlara sahip olması gerekir:

  • Beklenen bir durdurma zamanı olmalı; veya
  • Gorutineye durması gerektiğini belirten bir yol olmalı.

Her iki durumda da, kodun gorutinin tamamlanmasını bekleyip engelleyebileceği bir yol olmalı.

Örneğin:

Önerilmez:

go func() {
  for {
    flush()
    time.Sleep(delay)
  }
}()
// Bu gorutiniyi durdurmanın bir yolu yok. Uygulama sonlandırılana kadar sürekli olarak çalışacaktır.

Önerilen:

var (
  stop = make(chan struct{}) // Gorutineyi durdurmak için sinyal
  done = make(chan struct{}) // Gorutinin çıktığını belirten sinyal
)
go func() {
  defer close(done)
  ticker := time.NewTicker(delay)
  defer ticker.Stop()
  for {
    select {
    case <-ticker.C:
      flush()
    case <-stop:
      return
    }
  }
}()

// ... diğer kod
close(stop)  // Gorutinin durdurulmasını göster
<-done       // ve onun çıkmasını bekleyin

// Bu gorutini close(stop) ile bekletilebilir ve onun çıkmasını <-done ile bekleyebiliriz.

Gorutinlerin Çıkmasını Bekleme

Sistem tarafından oluşturulan bir gorutine verildiğinde, gorutinin çıkmasını beklemek için bir yol olmalıdır. Bunu başarmak için iki yaygın metot bulunmaktadır:

  • sync.WaitGroup kullanma. Birden fazla gorutini beklemesi gerektiğinde bu yöntemi kullanın.
var wg sync.WaitGroup
for i := 0; i < num; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        // ...
    }()
}
wg.Wait()
  • Başka bir chan struct{} eklenmesi ve gorutinin tamamlandıktan sonra kapatılması. Bir gorutin için sadece bir tane varsa bu yöntemi kullanın.
done := make(chan struct{})
go func() {
    defer close(done)
    // ...
}()
// Gorutinin tamamlanmasını beklemek için:
<-done

init() fonksiyonu gorutin oluşturmamalıdır. Ayrıca, init() kullanmaktan kaçının sayfasına bakınız.

Bir paketin arka planda bir gorutin gerektirmesi durumunda, gorutinin yaşam döngüsünü yönetmekten sorumlu bir nesne sunması gerekir. Bu nesne, arka plandaki gorutinin sonlandırılmasını ve çıkmasını beklemek için bir yöntem (Close, Stop, Shutdown, vb.) sağlamalıdır.

Önerilmeyen Yaklaşım:

func init() {
    go doWork()
}
func doWork() {
    for {
        // ...
    }
}
// Kullanıcı bu paketi dışa aktardığında, koşulsuz bir şekilde arka planda bir gorutin oluşturulur. Kullanıcı gorutini kontrol edemez veya durduramaz.

Önerilen Yaklaşım:

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 işçinin durmasını söyler
// ve onun tamamlanmasını bekler.
func (w *Worker) Shutdown() {
    close(w.stop)
    <-w.done
}

İşçi, kullanıcı tarafından istendiğinde yalnızca oluşturulmalı ve kullanıcının işçi tarafından kullanılan kaynakları serbest bırakabilmesi için işçiyi durduracak bir yöntem sağlamalıdır.

İşçinin birden fazla gorutiniyi yönettiği durumlarda, bir WaitGroup kullanılmalıdır.