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

القنوات (Channels) والاتصال

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

القنوات (Channels) هي آلية Go الأساسية للتواصل بين الـ goroutines. بدلاً من مشاركة الذاكرة بشكل مباشر، تقوم الـ goroutines بإرسال واستقبال القيم عبر القنوات، متبعةً فلسفة Go: "لا تتواصل عبر مشاركة الذاكرة؛ بل شارك الذاكرة عبر التواصل."

قم بإنشاء قناة باستخدام الدالة make مع الكلمة المفتاحية chan:

messages := make(chan string)

استخدم عامل السهم <- لإرسال واستقبال القيم. يشير السهم إلى اتجاه تدفق البيانات:

func main() {
    messages := make(chan string)
    
    go func() {
        messages <- "hello"  // إرسال إلى القناة
    }()
    
    msg := <-messages  // استقبال من القناة
    fmt.Println(msg)   // المخرجات: hello
}

بشكل افتراضي، تقوم عمليات الإرسال والاستقبال بـ الحظر (block) حتى يصبح الطرف الآخر جاهزاً. عندما تقوم الـ goroutine بإرسال "hello"، فإنها تنتظر حتى يصبح main جاهزاً للاستقبال. يوفر سلوك الحظر هذا مزامنة مدمجة — لا حاجة لاستخدام time.Sleep.

يمكنك إغلاق قناة للإشارة إلى أنه لن يتم إرسال المزيد من القيم. يمكن للمستقبلين اكتشاف ذلك:

close(messages)

msg, ok := <-messages
if !ok {
    fmt.Println("Channel closed")
}

القنوات ذات أنواع محددة—يمكن لـ chan string حمل السلاسل النصية فقط. تضمن سلامة الأنواع هذه تواصل الـ goroutines باستخدام أنواع البيانات الصحيحة، مما يسمح باكتشاف الأخطاء في وقت التجميع (compile time) بدلاً من وقت التشغيل (runtime).

challenge icon

التحدي

سهل

لنقم ببناء نظام ترحيل رسائل يوضح كيفية تواصل goroutines عبر القنوات (channels). ستقوم بإنشاء خط معالجة (pipeline) حيث تتدفق الرسائل بين مراحل معالجة مختلفة، مع تشغيل كل مرحلة بشكل متزامن.

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

  • relay.go: حدد خط معالجة الرسائل الخاص بك.

    قم بإنشاء هيكل Message يحتوي على حقول ID (int) و Content (string).

    قم بتنفيذ ثلاث دوال تمثل مراحل مختلفة من خط المعالجة الخاص بك:

    • Producer(messages []Message, out chan Message) - تأخذ شريحة (slice) من الرسائل وترسل كل واحدة منها إلى قناة الإخراج. بعد إرسال جميع الرسائل، أغلق القناة للإشارة إلى الإتمام.
    • Transformer(in chan Message, out chan Message) - تستقبل الرسائل من قناة الإدخال، وتحول محتوى كل رسالة إلى أحرف كبيرة (uppercase)، وترسل الرسالة المحولة إلى قناة الإخراج. عند إغلاق قناة الإدخال (يتم اكتشاف ذلك باستخدام صيغة الاستقبال ذات القيمتين)، أغلق قناة الإخراج.
    • Consumer(in chan Message) []string - تستقبل جميع الرسائل من قناة الإدخال وتجمعها في شريحة من النصوص المنسقة. يجب أن يتبع كل نص التنسيق التالي: Message [ID]: [Content]. قم بإرجاع الشريحة عند إغلاق القناة.
  • main.go: قم بإعداد خط المعالجة وتنظيم تدفق الرسائل المتزامن.

    اقرأ عدد الرسائل، ثم اقرأ معرف (ID) ومحتوى كل رسالة. أنشئ الرسائل وقم بإعداد قناتين لربط مراحل خط المعالجة الثلاث. قم بتشغيل Producer و Transformer كـ goroutines، ثم قم بتشغيل Consumer في الـ goroutine الرئيسي لجمع النتائج. اطبع كل نتيجة في سطر منفصل.

سيتم توفير المدخلات التالية:

  • السطر 1: عدد الرسائل (عدد صحيح)
  • الأسطر التالية: لكل رسالة، سطران - معرف الرسالة (عدد صحيح)، ثم محتواها (نص)

على سبيل المثال، إذا كان المعطى:

2
1
hello world
2
go channels

يجب أن يكون مخرجك:

Message 1: HELLO WORLD
Message 2: GO CHANNELS

يتدفق خط المعالجة كالتالي: Producer يرسل الرسائل ← Transformer يحولها إلى أحرف كبيرة ← Consumer يجمع النتائج وينسقها. يمثل كل سهم قناة تربط بين المراحل المتزامنة. استخدم حزمة strings لتحويل الأحرف إلى كبيرة.

ورقة مرجعية

تُعد القنوات (Channels) هي الآلية الأساسية في لغة Go للتواصل بين الـ goroutines. يمكنك إنشاء قناة باستخدام make مع الكلمة المفتاحية chan:

messages := make(chan string)

استخدم عامل السهم <- لإرسال واستقبال القيم. يشير السهم إلى اتجاه تدفق البيانات:

messages <- "hello"  // send to channel
msg := <-messages    // receive from channel

بشكل افتراضي، عمليات الإرسال والاستقبال تتوقف (block) حتى يكون الطرف الآخر جاهزاً، مما يوفر مزامنة مدمجة:

func main() {
    messages := make(chan string)
    
    go func() {
        messages <- "hello"  // send to channel
    }()
    
    msg := <-messages  // receive from channel
    fmt.Println(msg)   // Output: hello
}

قم بإغلاق القناة للإشارة إلى أنه لن يتم إرسال المزيد من القيم. يمكن للمستقبلين اكتشاف الإغلاق باستخدام صيغة الاستقبال ذات القيمتين:

close(messages)

msg, ok := <-messages
if !ok {
    fmt.Println("Channel closed")
}

القنوات ذات أنواع محددة (typed)—فالقناة من نوع chan string لا يمكنها سوى حمل النصوص (strings)، مما يوفر أماناً للأنواع في وقت التجميع (compile-time type safety).

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// قراءة عدد الرسائل
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// قراءة الرسائل
	messages := make([]Message, n)
	for i := 0; i < n; i++ {
		idLine, _ := reader.ReadString('\n')
		id, _ := strconv.Atoi(strings.TrimSpace(idLine))
		contentLine, _ := reader.ReadString('\n')
		content := strings.TrimSpace(contentLine)
		messages[i] = Message{ID: id, Content: content}
	}

	// TODO: إنشاء قناتين لربط مراحل خط الأنابيب (pipeline)

	// TODO: تشغيل المنتج (Producer) كـ goroutine

	// TODO: تشغيل المحول (Transformer) كـ goroutine

	// TODO: تشغيل المستهلك (Consumer) في الـ goroutine الرئيسية وجمع النتائج

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

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

جميع دروس Object Oriented Programming