פקודת Select
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 62 מתוך 107.
הפקודה select מאפשרת ל-goroutine להמתין בו-זמנית למספר פעולות בערוצים. היא דומה לפקודת switch, אבל כל מקרה כולל שליחה או קבלה בערוץ. כשכמה ערוצים מוכנים, select בוחרת אחד מהם באקראי.
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() { ch1 <- "from channel 1" }()
go func() { ch2 <- "from channel 2" }()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}ללא select, קבלה מ־ch1 תחסום עד להגעת נתונים, ועלולה לגרום להחמצת נתונים ב־ch2. עם select, הערוץ שבו מתקבלים נתונים ראשון מטופל.
מקרה ה־default הופך את select ללא חוסם. הוא מתבצע מיד אם אין ערוצים מוכנים:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
default:
fmt.Println("No message available")
}תבנית נפוצה משלבת את select עם פסקי זמן באמצעות time.After, שמחזירה ערוץ שמקבל ערך לאחר פרק הזמן שצוין:
select {
case result := <-ch:
fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout!")
}כך התוכנית שלך לא תמתין לנצח כשפעולה בערוץ עלולה לא להסתיים לעולם.
אתגר
קלבואו נבנה צג שירות שמשתמש בהוראת select כדי לטפל בכמה מקורות נתונים בו-זמנית. הצג יאזין לערוצים של שירותים שונים במקביל ויגיב בהתאם כאשר יגיעו נתונים או כשלא יהיו עדכונים זמינים.
תארגנו את הקוד בשני קבצים:
monitor.go: הגדירו את הלוגיקה לניטור השירות באמצעות טיפול בערוצים המבוסס על select.צרו מבנה
ServiceUpdateעם השדותName(string) ו-Status(string).ממשו פונקציה
Monitorשמקבלת שלושה ערוצים:primary chan ServiceUpdate,backup chan ServiceUpdateו-done chan bool. הפונקציה צריכה להשתמש ב-selectכדי להאזין לכל שלושת הערוצים בו-זמנית:- בעת קבלת נתונים מ-
primary: החזירו את המחרוזת המעוצבתPrimary: [Name] is [Status] - בעת קבלת נתונים מ-
backup: החזירו את המחרוזת המעוצבתBackup: [Name] is [Status] - בעת קבלת נתונים מ-
done: החזירוMonitoring stopped
ממשו פונקציה
CheckStatusשמקבלת ערוץ יחידupdates chan ServiceUpdateומשתמשת ב-selectעם מקרהdefaultכדי לבצע בדיקה לא חוסמת:- אם יש עדכון זמין: החזירו
Update available: [Name] - אם אין עדכון זמין (מקרה ברירת המחדל): החזירו
No updates pending
- בעת קבלת נתונים מ-
main.go: הגדירו את הערוצים והדגימו התנהגות של select חוסם ולא חוסם.קראו מהקלט מצב שקובע איזה תרחיש להפעיל:
primary,backup,stopאוcheck.במצבים
primary,backupו-stop: קראו גם שם שירות וסטטוס. צרו את שלושת הערוצים, הפעילו גורוטינה ששולחת נתונים לערוץ המתאים בהתאם למצב, לאחר מכן קראו ל-Monitorוהדפיסו את התוצאה.במצב
check: קראו שם שירות וסטטוס, וגם דגל (sendאוnosend) שמציין אם לשלוח נתונים לפני הבדיקה. צרו ערוץ מאוחסן בקיבולת 1. אם הדגל הואsend, שלחו עדכון לערוץ לפני הקריאה ל-CheckStatus. הדפיסו את התוצאה שלCheckStatus.
הקלט הבא יסופק:
- שורה 1: מצב (
primary,backup,stopאוcheck) - שורה 2: שם השירות
- שורה 3: סטטוס השירות
- שורה 4 (רק במצב
check):sendאוnosend
לדוגמה, עבור הקלט:
primary
Database
healthyהפלט שלכם צריך להיות:
Primary: Database is healthyועבור הקלט:
check
Cache
active
nosendהפלט שלכם צריך להיות:
No updates pendingנסו בעצמכם
package main
import (
"fmt"
)
func main() {
var mode string
fmt.Scanln(&mode)
var serviceName string
fmt.Scanln(&serviceName)
var serviceStatus string
fmt.Scanln(&serviceStatus)
// TODO: טפל במצבים השונים: "primary", "backup", "stop", "check"
// עבור המצבים "primary", "backup", "stop":
// - צור שלושה ערוצים: primary, backup, done
// - הפעל גורוטינה ששולחת נתונים לערוץ המתאים
// - קרא ל-Monitor והדפס את התוצאה
// עבור המצב "check":
// - קרא את הדגל הנוסף ("send" או "nosend")
// - צור ערוץ עם מאגר בקיבולת 1
// - אם הדגל הוא "send", שלח עדכון לערוץ
// - קרא ל-CheckStatus והדפס את התוצאה
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין