שיעורי מודול (4/5)
`sync.Mutex` ו-`sync.WaitGroup`
ערוצים הם הביטוי העיקרי של Go, אבל לפעמים מסורתי יותר
יש צורך בפרימיטיבים. החבילה sync מספקת מוטקסים, המתן
קבוצות וכלי שירות אחרים כדי לתאם מצב משותף.
sync.Mutex: מנעול בלעדי
import "sync"
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}רק אחד גורוטין בכל פעם נכנס בין Lock() ל-Unlock().
דפוס חובה: defer mu.Unlock() מיד אחרי Lock().
sync.RWMutex: קוראים רבים, כותב אחד
למצב קריאה כבד:
var mu sync.RWMutex
mu.RLock() // read lock: più goroutine simultanee OK
v := data
mu.RUnlock()
mu.Lock() // write lock: esclusivo
data = newValue
mu.Unlock()שימושי רק אם הקריאה באמת שולטת על הכתיבה; אחרת רגיל
sync.Mutex הוא פשוט יותר ולעתים קרובות מהיר יותר הודות לחלק הפנימי נמוך יותר
טענה.
sync.WaitGroup: המתן ל-N גורוטיינים
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
work(id)
}(i)
}
wg.Wait() // blocca finché tutti i Done() sono arrivatiזרימת עבודה:
wg.Add(N)מגדיל את המונה ב-N (לפני הפעלת הגורוטין).- כל גורוטינה קוראת ל-
wg.Done()כשהיא מסתיימת (באופן אידיאלי דרךdefer). wg.Wait()חסימות עד שהמונה יחזור ל-0.
sync.Once: אתחול עצלן בטוח בחוט
var (
once sync.Once
config *Config
)
func GetConfig() *Config {
once.Do(func() {
config = loadConfig()
})
return config
}ההתקשרות חזרה ב-once.Do פועלת בדיוק פעם אחת, אפילו בהזמנת
שיחות. דפוס אתחול יחיד / עצלן.
Mutex לעומת ערוץ: מתי להשתמש במה
הנחיות (גם מתוך השאלות הנפוצות הרשמיות של Go):
| מקרה | כלי מועדף |
|---|---|
| העברת בעלות על נתונים | ערוץ |
| חלוקת עבודה (תור עבודה) | ערוץ |
| תיאום גורוטינות עצמאיות | ערוץ |
| הגנה על שדה struct | Mutex |
| מטמון / מונה | Mutex או אטומי |
| הפניה לסינגלטון | sync.Once |
"ערוצים לתזמור, מוטקסים לנתונים משותפים" הוא כלל אצבע טוב.
גלאי גזע
הרץ מבחנים עם -race כדי לזהות מירוצי נתונים:
go test -race ./...
go run -race main.goגלאי הגזע מכשיר את הבינארי ורושם כל לא מסונכרן גישה לזיכרון משותף. זהו כלי מס' 1 לאימות קוד במקביל לפני הייצור.
נסה את זה
הגן על תוספת הספירה באמצעות mu.Lock() + defer mu.Unlock().
הצג רמז
defer mu.Unlock() מיד לאחר Lock() מבטיחה שחרור גם בעת פאניקה.
הפתרון זמין לאחר 3 ניסיונות
הפעל 3 גורוטינים והמתן לכולם באמצעות sync.WaitGroup (Add(3), Done() בכל goroutine, Wait()).
הצג רמז
הוסף לפני הפעלת הגורוטין; נעשה בתוך הגורוטין עם דחייה.
הפתרון זמין לאחר 3 ניסיונות
מהי הדפוס המומלץ כדי להבטיח שה-mutex ישוחרר?
mu.Lock()
// ?לְסַכֵּם
sync.Mutexמגן על מצב משותף; תמידdefer mu.Unlock().- השתמש במקלט מצביע כדי להימנע מהעתקת המוטקס.
sync.RWMutexלדפוסים של קוראים רבים/סופר אחד (רק אם זה עוזר).sync.WaitGroup:Addלפניgo,Doneעםdefer,Waitלחכות.sync.Once: אתחול עצלן בטוח בשרשור.- "ערוצים לתזמור, מוטקסים לנתונים משותפים".
go test -race: כלי מס' 1 לאיתור מירוצי נתונים.