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() לכתיבה. כך מתאפשרות קריאות במקביל, תוך הבטחת בטיחות הכתיבה.
אתגר
קלבואו נבנה מערכת מלאי בטוחה לשימוש במקביל, שעוקבת אחר רמות המלאי של מוצרים. המערכת שלכם תטפל בבטחה בקריאות ובכתיבות מקבילות באמצעות 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 stockremove: אם הפעולה הצליחה, הדפיסו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: קראו את הכמות, הסירו מהמלאי, הדפיסו את התוצאה המתאימה
}
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי2העמקה בסוגים וב-Structs
סוגים בסיסיים ומורכביםהגדרות של סוגים מותאמים אישיתתגיות StructStructs אנונימייםStructs מקונניםערכי אפס וברירות מחדלסיכום – ספר אנשי קשר5קומפוזיציה במקום ירושה
למה ב-Go אין ירושהיסודות הטמעת Structקידום מתודותהטמעת כמה Structsהטמעה לעומת אגרגציההסתרת מתודות מוטמעותסיכום – היררכיית עובדים8טיפול בשגיאות ו-OOP
ממשק errorסוגי שגיאות מותאמים אישיתעטיפת שגיאות (fmt.Errorf)שגיאות Sentinelerrors.Is() ו-errors.As()Panic, Defer ו-Recoverסיכום – מנתח קבצים11ספרייה סטנדרטית ותכנות מונחה עצמים
io.Reader ו-io.Writersort.Interfaceהממשק fmt.Stringerencoding/json עם מבניםהממשק http.Handlerסיכום – מודלים של REST API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין