القنوات (Channels) والاتصال
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 60 من 107.
القنوات هي الآلية الأساسية في 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
}بشكل افتراضي، تؤدي عمليات الإرسال والاستقبال إلى الحظر حتى يصبح الطرف الآخر جاهزًا. عندما ترسل "hello" الـgoroutine، فإنها تنتظر حتى تصبح main جاهزة للاستقبال. يوفر سلوك الحظر هذا مزامنة مضمّنة: لا حاجة إلى time.Sleep.
يمكنك إغلاق channel للإشارة إلى أنه لن يتم إرسال أي قيم أخرى. يمكن للمستقبِلين اكتشاف ذلك:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}القنوات محددة النوع: لا يمكن لـ chan string حمل سوى السلاسل النصية. يضمن أمان الأنواع هذا أن تتواصل goroutine مع أنواع البيانات الصحيحة، مع اكتشاف الأخطاء في وقت الترجمة بدلًا من وقت التشغيل.
التحدي
سهللنَبْنِ نظام ترحيل للرسائل يوضّح كيفية تواصل goroutine من خلال القنوات. ستنشئ pipeline تتدفق فيها messages بين مراحل معالجة مختلفة، مع تشغيل كل مرحلة بشكل متزامن.
ستنظّم شفرتك عبر ملفين:
relay.go: Define pipeline معالجة الرسائل.أنشئ بنية
Messageتحتوي على الحقلينID(int) وContent(string).نفّذ ثلاث دوال تمثّل مراحل مختلفة من pipeline:
Producer(messages []Message, out chan Message)- تستقبل شريحة من messages وترسل كل واحدة منها إلى قناة الإخراج. بعد إرسال جميع messages، أغلق القناة للإشارة إلى اكتمال العملية.Transformer(in chan Message, out chan Message)- تستقبل messages من قناة الإدخال، وتحول محتوى كل message إلى أحرف كبيرة، ثم ترسل message المحوّلة إلى قناة الإخراج. عند إغلاق قناة الإدخال (يُكتشف ذلك باستخدام صيغة الاستقبال ذات القيمتين)، أغلق قناة الإخراج.Consumer(in chan Message) []string- تستقبل جميع messages من قناة الإدخال وتجمعها في شريحة من السلاسل المنسّقة. يجب أن تتبع كل سلسلة التنسيق:Message [ID]: [Content]. أعد الشريحة عند إغلاق القناة.
main.go: أعدّ pipeline ونظّم تدفق messages المتزامن.اقرأ عدد messages، ثم اقرأ معرّف ومحتوى كل message. أنشئ messages وأعدّ قناتين لربط مراحل pipeline الثلاث. شغّل Producer وTransformer بوصفهما goroutines، ثم شغّل Consumer في goroutine الرئيسي لجمع النتائج. اطبع كل نتيجة في سطر منفصل.
سيتم توفير المدخلات التالية:
- السطر 1: عدد messages (عدد صحيح)
- الأسطر التالية: لكل message، سطران - معرّف message (عدد صحيح)، ثم محتواه (سلسلة نصية)
على سبيل المثال، إذا أُعطي:
2
1
hello world
2
go channelsفيجب أن يكون الناتج:
Message 1: HELLO WORLD
Message 2: GO CHANNELSيتدفق pipeline على النحو التالي: Producer يرسل messages → Transformer يحوّلها إلى أحرف كبيرة → Consumer يجمع النتائج وينسّقها. يمثّل كل سهم قناة تربط بين مراحل متزامنة. استخدم الحزمة strings لإجراء التحويل إلى أحرف كبيرة.
جرّب بنفسك
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: أنشئ قناتين لربط مراحل خط الأنابيب
// TODO: أطلق Producer كـ goroutine
// TODO: أطلق Transformer كـ goroutine
// TODO: شغّل Consumer في الـ goroutine الرئيسي واجمع النتائج
// TODO: اطبع كل نتيجة في سطر منفصل
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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تدرّب بنفسك: مترجم Go عبر الإنترنت