การจัดการ Goroutine
Goroutine เป็นกระบวนการที่มีน้ำหนักเบา แต่ไม่ได้ฟรี: อย่างน้อยแล้ว พวกมันกินหน่วยความจำสำหรับ stack และการจัดตาราง CPU แม้ว่าค่าใช้จ่ายเหล่านี้น้อยเมื่อใช้ Goroutines อย่างไรก็ตาม การสร้างมันในจำนวนมากๆ โดยไม่มีการควบคุมชีวิตจริงๆ อาจเสี่ยงทำให้เกิดปัญหาด้านประสิทธิภาพ ยอด Goroutines ที่ไม่มีการควบคุมอาจทำให้เกิดปัญหาอื่นๆ เช่น ขัดขวางวัตถุที่ไม่ได้ใช้ทำให้ไม่มีการเก็บขยะและเชื่อมต่อทรัพยากรที่ไม่ได้ใช้
ดังนั้น อย่างของ JSON ที่ย่อย Goroutines ในโค้ดของคุณ ใช้ go.uber.org/goleak เพื่อทดสอบการรั่ว Goroutine ในแพ็คเกจที่อาจสร้าง Goroutine
โดยทั่วไป แต่ละ Goroutine ต้อง:
- มีเวลาในการหยุดที่สามารถทำนายได้; หรือ
- มีวิธีในการส่งสัญญาณให้ Goroutine ว่าควรหยุด
ในทั้งสองกรณี ต้องมีวิธีในการบล็อกโค้ดและรอให้ Goroutine ทำงานเสร็จสิ้น
ตัวอย่างเช่น:
ไม่แนะนำ:
go func() {
for {
flush()
time.Sleep(delay)
}
}()
// ไม่มีวิธีให้อยู่สินะ Goroutine นี้ มันกำลังทำงานไปตลอด โจทย์จะวิ่งไปตลอดจนกว่าแอปพลิเคชันจะปิด
แนะนำ:
var (
stop = make(chan struct{}) // ส่งสัญญาณให้ Goroutine หยุด
done = make(chan struct{}) // ส่งสัญญาณให้ Goroutine ออก
)
go func() {
defer close(done)
ticker := time.NewTicker(delay)
defer ticker.Stop()
for {
select {
case <-ticker.C:
flush()
case <-stop:
return
}
}
}()
// ... โค้ดอื่นๆ
close(stop) // บ่งชี้ให้ Goroutine หยุด
<-done // และรอให้มันออก
// สามารถรอ Goroutine ด้วย close(stop) และเราสามารถรอให้มันออก <-done
การรอให้ Goroutines ออก
เมื่อมี Goroutine ที่สร้างขึ้นโดยระบบ จะต้องมีวิธีการรอให้ Goroutine ออก มีวิธีการที่สองที่สามารถทำได้:
- การใช้
sync.WaitGroup
ใช้วิธีนี้เมื่อต้องรอหลาย Goroutines
var wg sync.WaitGroup
for i := 0; i < num; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// ...
}()
}
wg.Wait()
- เพิ่ม
chan struct{}
อีกชุดและทำการปิดหลังจาก Goroutine เสร็จสิ้น ใช้วิธีนี้เมื่อมีแค่ Goroutine เดียว
done := make(chan struct{})
go func() {
defer close(done)
// ...
}()
// เพื่อรอให้ Goroutine เสร็จสิ้น:
<-done
init()
ฟังก์ชันไม่ควรสร้าง Goroutines อย่างไรก็ตาม อ่านเพิ่มเติมที่ หลีกเลี่ยงการใช้ init()
หากแพ็คเกจต้องการ Goroutine แบ็คกราวด์ จะต้องแสดงวัตถุที่รับผิดชอบในการจัดการชีวิตการทำงานของ Goroutine วัตถุนี้ต้องมีวิธี (ปิด
, หยุด
, ปิดเสียง
ฯลฯ) เพื่อบ่งบอกถึงการสิ้นสุดของ Goroutine แบ็คกราวด์และรอให้มันออก
ไม่แนะนำ:
func init() {
go doWork()
}
func doWork() {
for {
// ...
}
}
// เมื่อผู้ใช้ส่งออกแพ็กเกจนี้ จะสร้าง Goroutine แบ็คกราวด์โดยไม่เงียบงันได้ ผู้ใช้ไม่สามารถควบคุม Goroutine หรือหยุดมัน
แนะนำ:
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
}
}
}
// ปิดบอกผู้ใช้ให้หยุ Goroutine
// และรอให้มันเสร็จสิ้น
func (w *Worker) Shutdown() {
close(w.stop)
<-w.done
}
สร้าง Worker เมื่อมีคำขอจากผู้ใช้เท่านั้น และมีเมธอดให้ปิด Worker เพื่อให้ผู้ใช้ปล่อยทรัพยากรที่ใช้โดย Worker
โปรดทราบว่าหาก Worker จัดการกับ Goroutines หลายๆ ตัว ควรใช้ WaitGroup