Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

القنوات المخزنة (Buffered) مقابل غير المخزنة

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 61 من 107.

القنوات التي استخدمناها حتى الآن غير مخزنة—ليس لديها سعة للاحتفاظ بالقيم. عملية الإرسال تتوقف (blocks) حتى يقوم goroutine آخر بالاستقبال، والعكس صحيح. القنوات المخزنة تضيف مساحة تخزين داخلية، مما يسمح لعمليات الإرسال بالاكتمال دون وجود مستقبل فوري.

أنشئ قناة مخزنة (buffered channel) عن طريق تمرير سعة إلى make:

// غير مخزن مؤقتًا - السعة 0
unbuffered := make(chan int)

// مخزن مؤقتًا - السعة 3
buffered := make(chan int, 3)

في القنوات ذات التخزين المؤقت (buffered channel)، تتوقف عمليات الإرسال فقط عندما يكون التخزين المؤقت ممتلئاً، وتتوقف عمليات الاستقبال فقط عندما يكون التخزين المؤقت فارغاً:

ch := make(chan string, 2)

ch <- "first"   // لا يحظر - المخزن المؤقت به مساحة
ch <- "second"  // لا يحظر - المخزن المؤقت به مساحة
// ch <- "third" سيحظر - المخزن المؤقت ممتلئ

fmt.Println(<-ch)  // "first"
fmt.Println(<-ch)  // "second"

استخدم len(ch) للتحقق من عدد العناصر الموجودة حاليًا في الذاكرة المؤقتة (buffer)، و cap(ch) للسعة الإجمالية.

متى تستخدم كل منهما: توفر القنوات غير المخزنة (Unbuffered channels) مزامنة وثيقة—حيث يعرف المرسل أن المستقبل قد تلقى القيمة. أما القنوات المخزنة (Buffered channels) فتقوم بفصل توقيت المرسل والمستقبل، وهي مفيدة عندما يعمل المنتجون والمستهلكون بسرعات مختلفة. ومع ذلك، يمكن للقنوات المخزنة أن تخفي أخطاء المزامنة، لذا يفضل استخدام القنوات غير المخزنة ما لم يكن لديك سبب محدد لاستخدام التخزين المؤقت.

challenge icon

التحدي

سهل

لنقم ببناء معالج دفعات (batch processor) يوضح الفرق بين القنوات المخزنة مؤقتاً (buffered channels) وغير المخزنة مؤقتاً. ستقوم بإنشاء نظام حيث يرسل المنتج (producer) عناصر إلى المعالج، باستخدام القنوات المخزنة مؤقتاً لفصل توقيت عملهما والسماح للمنتج بالعمل مسبقاً.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • 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 - تستقبل العناصر من قناة الإدخال وتجمعها في شريحة (slice) من السلاسل النصية المنسقة. لكل عنصر مستلم، يجب أن تكون السلسلة النصية: Consumed: [ID] - [Value]. أعد الشريحة عند إغلاق القناة.
  • main.go: قم بإعداد القناة المخزنة مؤقتاً وتنسيق عمل المنتج والمستهلك.

    اقرأ سعة المخزن المؤقت (buffer capacity)، ثم عدد العناصر، متبوعاً بمعرف (ID) وقيمة كل عنصر. أنشئ قناة مخزنة مؤقتاً بالسعة المحددة. قم بتشغيل Producer أولاً (ليس كـ goroutine) لملء المخزن المؤقت، ثم قم بتشغيل 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، يمكن للمنتج إرسال جميع العناصر الثلاثة دون توقف (blocking)، ويظهر المخزن المؤقت 3 عناصر منتظرة قبل أن يبدأ المستهلك. إذا كان المخزن المؤقت أصغر من عدد العناصر، فسيتم إيقاف المنتج مؤقتاً بانتظار توفر مساحة.

ورقة مرجعية

يمكن أن تكون القنوات (Channels) في لغة Go غير مخزنة مؤقتاً (سعة 0) أو مخزنة مؤقتاً (مع تخزين داخلي).

قم بإنشاء القنوات باستخدام make:

// غير مخزنة مؤقتاً - السعة 0
unbuffered := make(chan int)

// مخزنة مؤقتاً - السعة 3
buffered := make(chan int, 3)

سلوك القنوات المخزنة مؤقتاً:

  • عمليات الإرسال تُحظر فقط عندما يكون المخزن المؤقت ممتلئاً
  • عمليات الاستقبال تُحظر فقط عندما يكون المخزن المؤقت فارغاً
ch := make(chan string, 2)

ch <- "first"   // لا يحظر - المخزن المؤقت به مساحة
ch <- "second"  // لا يحظر - المخزن المؤقت به مساحة
// ch <- "third" would block - buffer is full // سيحظر - المخزن المؤقت ممتلئ

fmt.Println(<-ch)  // "first"
fmt.Println(<-ch)  // "second"

التحقق من حالة المخزن المؤقت:

  • len(ch) - عدد العناصر الموجودة حالياً في المخزن المؤقت
  • cap(ch) - السعة الإجمالية للمخزن المؤقت

متى تستخدم:

  • غير مخزنة مؤقتاً: مزامنة دقيقة — يعرف المرسل أن المستقبل قد استلم القيمة
  • مخزنة مؤقتاً: فصل توقيت المرسل والمستقبل عندما يعملان بسرعات مختلفة

جرّب بنفسك

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))

	// قراءة معرف وقيمة كل عنصر
	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: تشغيل المنتج (ليس كـ goroutine) لملء المخزن المؤقت

	// TODO: تشغيل المستهلك لمعالجة جميع العناصر

	// TODO: طباعة كل نتيجة مستهلكة في سطر منفصل
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming