مدیریت گوروتین
گوروتینها سبک هستند، اما رایگان نیستند: حداقل حافظهای برای پشته و برنامهریزی 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) قابل انتظار است و ما میتوانیم به انتظار آن بمانیم تا <1.> خاتمه یابد.
انتظار برای خاتمه گوروتینها
هنگامی که یک گوروتین تولید شده توسط سیستم داده شود، باید راهی برای انتظار خاتمه گوروتین وجود داشته باشد. دو روش متداول برای رسیدن به این هدف وجود دارند:
- استفاده از
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 به کارگر میگوید که باید متوقف شود
// و به منتظر شدن برای تکمیل آن.
func (w *Worker) Shutdown() {
close(w.stop)
<-w.done
}
زمانی که کاربر درخواست کارگر را ایجاد میکند، و یک روش برای خاموش کردن کارگر ارائه دهد تا کاربر بتواند منابع استفاده شده توسط کارگر را آزاد کند.
توجه داشته باشید که اگر کارگر چندین گوروتین را مدیریت میکند، باید از WaitGroup استفاده شود.