דילוג לתוכן המרכזי
מודול 7 · שיעור 4 מתוך 534/50 בקורס~14 min
שיעורי מודול (4/5)

`sync.Mutex` ו-`sync.WaitGroup`

ערוצים הם הביטוי העיקרי של Go, אבל לפעמים מסורתי יותר יש צורך בפרימיטיבים. החבילה sync מספקת מוטקסים, המתן קבוצות וכלי שירות אחרים כדי לתאם מצב משותף.

sync.Mutex: מנעול בלעדי

Go
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: קוראים רבים, כותב אחד

למצב קריאה כבד:

Go
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 גורוטיינים

Go
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

זרימת עבודה:

  1. wg.Add(N) מגדיל את המונה ב-N (לפני הפעלת הגורוטין).
  2. כל גורוטינה קוראת ל-wg.Done() כשהיא מסתיימת (באופן אידיאלי דרך defer).
  3. wg.Wait() חסימות עד שהמונה יחזור ל-0.

sync.Once: אתחול עצלן בטוח בחוט

Go
var (
    once   sync.Once
    config *Config
)

func GetConfig() *Config {
    once.Do(func() {
        config = loadConfig()
    })
    return config
}

ההתקשרות חזרה ב-once.Do פועלת בדיוק פעם אחת, אפילו בהזמנת שיחות. דפוס אתחול יחיד / עצלן.

Mutex לעומת ערוץ: מתי להשתמש במה

הנחיות (גם מתוך השאלות הנפוצות הרשמיות של Go):

מקרהכלי מועדף
העברת בעלות על נתוניםערוץ
חלוקת עבודה (תור עבודה)ערוץ
תיאום גורוטינות עצמאיותערוץ
הגנה על שדה structMutex
מטמון / מונהMutex או אטומי
הפניה לסינגלטוןsync.Once

"ערוצים לתזמור, מוטקסים לנתונים משותפים" הוא כלל אצבע טוב.

גלאי גזע

הרץ מבחנים עם -race כדי לזהות מירוצי נתונים:

Bash
go test -race ./...
go run -race main.go

גלאי הגזע מכשיר את הבינארי ורושם כל לא מסונכרן גישה לזיכרון משותף. זהו כלי מס' 1 לאימות קוד במקביל לפני הייצור.

נסה את זה

פעילות גופנית#go.m7.l4.e1
ניסיונות: 0טוען...

הגן על תוספת הספירה באמצעות mu.Lock() + defer mu.Unlock().

טוען עורך...
הצג רמז

defer mu.Unlock() מיד לאחר Lock() מבטיחה שחרור גם בעת פאניקה.

הפתרון זמין לאחר 3 ניסיונות

פעילות גופנית#go.m7.l4.e2
ניסיונות: 0טוען...

הפעל 3 גורוטינים והמתן לכולם באמצעות sync.WaitGroup (Add(3), Done() בכל goroutine, Wait()).

טוען עורך...
הצג רמז

הוסף לפני הפעלת הגורוטין; נעשה בתוך הגורוטין עם דחייה.

הפתרון זמין לאחר 3 ניסיונות

Quiz#go.m7.l4.e3
מוכן

מהי הדפוס המומלץ כדי להבטיח שה-mutex ישוחרר?

Go
mu.Lock()
// ?
אפשרויות תשובה

לְסַכֵּם

  • sync.Mutex מגן על מצב משותף; תמיד defer mu.Unlock().
  • השתמש במקלט מצביע כדי להימנע מהעתקת המוטקס.
  • sync.RWMutex לדפוסים של קוראים רבים/סופר אחד (רק אם זה עוזר).
  • sync.WaitGroup: Add לפני go, Done עם defer, Wait לחכות.
  • sync.Once: אתחול עצלן בטוח בשרשור.
  • "ערוצים לתזמור, מוטקסים לנתונים משותפים".
  • go test -race: כלי מס' 1 לאיתור מירוצי נתונים.