Channel עם חוצץ לעומת ללא חוצץ
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 61 מתוך 107.
הערוצים שבהם השתמשנו עד כה הם ללא חציצה. אין להם קיבולת לאחסון ערכים. פעולת שליחה נחסמת עד שגורוטינה אחרת מקבלת את הערך, ולהפך. ערוצים עם חציצה מוסיפים אחסון פנימי, ומאפשרים לפעולות שליחה להסתיים בלי שמקבל יהיה זמין מיד.
יוצרים ערוץ עם חוצץ על ידי העברת קיבולת אל make:
// ללא חוצץ - קיבולת 0
unbuffered := make(chan int)
// עם חוצץ - קיבולת 3
buffered := make(chan int, 3)בערוץ עם חוצץ, פעולות שליחה נחסמות רק כשהחוצץ מלא, ופעולות קבלה נחסמות רק כשהחוצץ ריק:
ch := make(chan string, 2)
ch <- "first" // לא נחסם - יש מקום בחוצץ
ch <- "second" // לא נחסם - יש מקום בחוצץ
// ch <- "third" היה נחסם - החוצץ מלא
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"השתמשו ב־len(ch) כדי לבדוק כמה פריטים יש כרגע במאגר, וב־cap(ch) כדי לבדוק את הקיבולת הכוללת.
מתי להשתמש בכל אחד מהם: ערוצים ללא חציצה מספקים סנכרון הדוק: השולח יודע שהמקבל קיבל את הערך. ערוצים עם חציצה מנתקים את התזמון של השולח ושל המקבל, והם שימושיים כאשר יצרנים וצרכנים עובדים במהירויות שונות. עם זאת, ערוצים עם חציצה עלולים להסוות באגים בסנכרון, לכן העדף ערוצים ללא חציצה, אלא אם יש לך סיבה מסוימת להשתמש בחציצה.
אתגר
קלבואו נבנה מעבד אצווה שמדגים את ההבדל בין ערוצים עם חוצץ לערוצים ללא חוצץ. תיצרו מערכת שבה יצרן שולח פריטים למעבד, באמצעות ערוצים עם חוצץ כדי להפריד בין קצב הפעולה שלהם ולאפשר ליצרן להתקדם.
תארגנו את הקוד שלכם בשני קבצים:
processor.go: הגדירו את לוגיקת עיבוד האצווה באמצעות ערוצים.צרו מבנה
Itemעם השדותID(int) ו-Value(string).ממשו שתי פונקציות:
Producer(items []Item, out chan Item)- שולחת כל פריט לערוץ הפלט. לאחר שליחת כל פריט, הדפיסו:Produced item [ID]. לאחר שליחת כל הפריטים, הדפיסו את מספר הפריטים שממתינים כרגע בחוצץ באמצעותlen(out)בפורמט:Buffer has [count] items. לאחר מכן סגרו את הערוץ.Consumer(in chan Item) []string- מקבלת פריטים מערוץ הקלט ואוספת אותם לפרוסה של מחרוזות מעוצבות. עבור כל פריט שהתקבל, המחרוזת צריכה להיות:Consumed: [ID] - [Value]. החזירו את הפרוסה כשהערוץ נסגר.
main.go: הגדירו את הערוץ עם החוצץ ותאמו בין היצרן לצרכן.קראו את קיבולת החוצץ, אחר כך את מספר הפריטים, ולאחר מכן את המזהה והערך של כל פריט. צרו ערוץ עם חוצץ בקיבולת שצוינה. הריצו תחילה את Producer (לא כגורוטינה) כדי למלא את החוצץ, ואז הריצו את Consumer כדי לעבד את כל הפריטים. הדפיסו כל תוצאה שהתקבלה בשורה נפרדת.
הקלטים הבאים יסופקו:
- שורה 1: קיבולת החוצץ (מספר שלם)
- שורה 2: מספר הפריטים (מספר שלם)
- השורות הבאות: עבור כל פריט, שתי שורות — מזהה הפריט (מספר שלם), ואז הערך שלו (מחרוזת)
לדוגמה, בהינתן:
3
3
1
apple
2
banana
3
cherryהפלט שלכם צריך להיות:
Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherryשימו לב שכאשר קיבולת החוצץ היא 3, היצרן יכול לשלוח את כל 3 הפריטים בלי להיחסם, והחוצץ מציג 3 פריטים שממתינים לפני שהצרכן מתחיל. אם החוצץ היה קטן ממספר הפריטים, היצרן היה נחסם וממתין למקום פנוי.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// קרא את קיבולת המאגר
line, _ := reader.ReadString('\n')
bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))
// קרא את מספר הפריטים
line, _ = reader.ReadString('\n')
numItems, _ := strconv.Atoi(strings.TrimSpace(line))
// קרא את ה-ID והערך של כל פריט
items := make([]Item, numItems)
for i := 0; i < numItems; i++ {
line, _ = reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(line))
line, _ = reader.ReadString('\n')
value := strings.TrimSpace(line)
items[i] = Item{ID: id, Value: value}
}
// TODO: צור ערוץ עם מאגר בקיבולת שצוינה
// TODO: הפעל את Producer (לא כ-goroutine) כדי למלא את המאגר
// TODO: הפעל את Consumer כדי לעבד את כל הפריטים
// 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 אונליין