القنوات (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).
التحدي
سهللنقم ببناء نظام ترحيل رسائل يوضح كيفية تواصل 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: طباعة كل نتيجة في سطر منفصل
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool