Menu
Coddy logo textTech

sync.Mutex ו-sync.RWMutex

חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 63 מתוך 107.

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

ל־sync.Mutex יש שתי מתודות: Lock() ו־Unlock(). כשגורוטינה קוראת ל־Lock(), היא מקבלת גישה בלעדית. גורוטינות אחרות שקוראות ל־Lock() ייחסמו עד שתיקרא Unlock():

type Counter struct {
    mu    sync.Mutex
    value int
}
func (c *Counter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}
func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

השימוש ב־defer c.mu.Unlock() מבטיח שהנעילה תשוחרר גם אם הפונקציה תחזור מוקדם או תיכנס למצב פאניקה: זהו דפוס נפוץ ומומלץ.

כאשר פעולות הקריאה תכופות אך פעולות הכתיבה נדירות, sync.RWMutex מציע ביצועים טובים יותר. הוא מאפשר לכמה קוראים לגשת בו-זמנית, אך הכותבים מקבלים גישה בלעדית:

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}
func (c *Cache) Get(key string) string {
    c.mu.RLock()         // מותרת גישה למספר קוראים בו־זמנית
    defer c.mu.RUnlock()
    return c.data[key]
}
func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // גישה בלעדית לכתיבה
    defer c.mu.Unlock()
    c.data[key] = value
}

השתמשו ב־RLock()/RUnlock() לפעולות קריאה וב־Lock()/Unlock() לכתיבה. כך מתאפשרות קריאות במקביל, תוך הבטחת בטיחות הכתיבה.

challenge icon

אתגר

קל

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

תארגנו את הקוד שלכם בשני קבצים:

  • inventory.go: הגדירו את מערכת ניהול המלאי הבטוחה לשימוש במקביל.

    צרו מבנה Inventory ששומר כמויות של מוצרים במפה ומשתמש ב-sync.RWMutex כדי להגן על הגישה. מערכת המלאי שלכם צריכה לתמוך בפעולות הבאות:

    • NewInventory() *Inventory - יוצר מערכת מלאי חדשה עם מפה מאותחלת
    • AddStock(product string, quantity int) - מוסיף כמות למלאי של מוצר (השתמשו בנעילה בלעדית, מכיוון שהפעולה משנה נתונים)
    • GetStock(product string) int - מחזיר את המלאי הנוכחי של מוצר, או 0 אם הוא לא נמצא (השתמשו בנעילת קריאה, מכיוון שהפעולה רק קוראת נתונים)
    • RemoveStock(product string, quantity int) bool - מסיר כמות מהמלאי אם יש מספיק ממנו. מחזיר true אם הפעולה הצליחה, ו-false אם אין מספיק במלאי (השתמשו בנעילה בלעדית)

    זכרו להשתמש ב-defer כדי לשחרר את הנעילה, ולוודא שהנעילות תמיד משתחררות כראוי.

  • main.go: קראו את הפעולות והדגימו את השימוש במערכת המלאי הבטוחה לשימוש במקביל.

    קראו את מספר הפעולות, ואז עבדו על כל אחת מהן. לכל פעולה יש סוג (add, get או remove), שם מוצר, ובפעולות add/remove גם כמות.

    הדפיסו את התוצאה של כל פעולה:

    • add: הדפיסו Added [quantity] [product]
    • get: הדפיסו [product]: [stock] in stock
    • remove: אם הפעולה הצליחה, הדפיסו Removed [quantity] [product]; אחרת, הדפיסו Insufficient stock for [product]

הקלט הבא יסופק:

  • שורה 1: מספר הפעולות (מספר שלם)
  • השורות הבאות: לכל פעולה:
    • סוג הפעולה (add, get או remove)
    • שם המוצר
    • כמות (רק עבור פעולות add ו-remove)

לדוגמה, עבור הקלט:

5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
apples

הפלט שלכם אמור להיות:

Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stock

ה-RWMutex מאפשר לכמה קריאות של GetStock לקרוא בו-זמנית, בעוד ש-AddStock ו-RemoveStock מקבלות גישה בלעדית כאשר הן משנות את המלאי.

נסו בעצמכם

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// קראו את מספר הפעולות
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// צרו מלאי חדש
	inventory := NewInventory()
	
	// עבדו כל פעולה
	for i := 0; i < numOps; i++ {
		// קראו את סוג הפעולה
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		// קראו את שם המוצר
		productLine, _ := reader.ReadString('\n')
		product := strings.TrimSpace(productLine)
		
		// TODO: טפלו בכל סוג פעולה (add, get, remove)
		// עבור "add" ו-"remove", קראו את הכמות מהקלט
		// קראו למתודה המתאימה של המלאי
		// הדפיסו את התוצאה בהתאם לתיאור האתגר
		
		switch opType {
		case "add":
			// TODO: קראו את הכמות, הוסיפו למלאי, הדפיסו את התוצאה
			
		case "get":
			// TODO: קבלו את הכמות במלאי, הדפיסו את התוצאה
			
		case "remove":
			// TODO: קראו את הכמות, הסירו מהמלאי, הדפיסו את התוצאה המתאימה
			
		}
	}
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Go אונליין