Menu
Coddy logo textTech

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) כדי לבדוק את הקיבולת הכוללת.

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

challenge icon

אתגר

קל

בואו נבנה מעבד אצווה שמדגים את ההבדל בין ערוצים עם חוצץ לערוצים ללא חוצץ. תיצרו מערכת שבה יצרן שולח פריטים למעבד, באמצעות ערוצים עם חוצץ כדי להפריד בין קצב הפעולה שלהם ולאפשר ליצרן להתקדם.

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

  • 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: הדפס כל תוצאה שנצרכה בשורה נפרדת
}
quiz iconבחנו את עצמכם

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

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

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