جملة Select
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 62 من 107.
تتيح عبارة select لـ goroutine الانتظار على عمليات قنوات (channels) متعددة في وقت واحد. إنها تشبه عبارة switch، ولكن كل حالة (case) تتضمن عملية إرسال أو استقبال عبر القناة. عندما تكون قنوات متعددة جاهزة، تختار select واحدة بشكل عشوائي.
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() { ch1 <- "from channel 1" }()
go func() { ch2 <- "from channel 2" }()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}بدون select، فإن الاستقبال من ch1 سيؤدي إلى الحظر حتى وصول البيانات، مما قد يؤدي إلى فقدان البيانات في ch2. باستخدام select، يتم التعامل مع أي قناة تستقبل البيانات أولاً.
تجعل الحالة default تعليمة select غير حاجبة (non-blocking)—فهي تُنفذ فوراً إذا لم تكن هناك قنوات جاهزة:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
default:
fmt.Println("No message available")
}يجمع نمط شائع بين select ومهلات زمنية باستخدام time.After، والذي يُرجع قناة تستقبل قيمة بعد المدة المحددة:
select {
case result := <-ch:
fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout!")
}هذا يمنع برنامجك من الانتظار إلى الأبد عندما قد لا تكتمل عملية القناة أبداً.
التحدي
سهللنقم ببناء مراقب خدمة يستخدم عبارة select للتعامل مع مصادر بيانات متعددة متزامنة. سيقوم المراقب الخاص بك بالاستماع إلى قنوات خدمة مختلفة في وقت واحد والاستجابة بشكل مناسب عند وصول البيانات أو عند عدم توفر تحديثات.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
monitor.go: حدد منطق مراقبة الخدمة الخاص بك مع معالجة القنوات القائمة على select.قم بإنشاء هيكل
ServiceUpdateمع حقولName(string) وStatus(string).قم بتنفيذ دالة
Monitorتأخذ ثلاث قنوات:primary chan ServiceUpdate، وbackup chan ServiceUpdate، وdone chan bool. يجب أن تستخدم هذه الدالةselectللاستماع إلى جميع القنوات الثلاث في وقت واحد:- عند الاستلام من
primary: قم بإرجاع السلسلة المنسقةPrimary: [Name] is [Status] - عند الاستلام من
backup: قم بإرجاع السلسلة المنسقةBackup: [Name] is [Status] - عند الاستلام من
done: قم بإرجاعMonitoring stopped
قم بتنفيذ دالة
CheckStatusتأخذ قناة واحدةupdates chan ServiceUpdateوتستخدمselectمع حالةdefaultلإجراء فحص غير حاجب (non-blocking):- إذا كان التحديث متاحاً: قم بإرجاع
Update available: [Name] - إذا لم يكن هناك تحديث متاح (الحالة الافتراضية): قم بإرجاع
No updates pending
- عند الاستلام من
main.go: قم بإعداد القنوات وتوضيح سلوك select الحاجب وغير الحاجب.اقرأ وضعاً (mode) من الإدخال يحدد السيناريو الذي سيتم تشغيله:
primary، أوbackup، أوstop، أوcheck.بالنسبة للأوضاع
primaryوbackupوstop: اقرأ أيضاً اسم الخدمة وحالتها. قم بإنشاء القنوات الثلاث، وابدأ goroutine يرسل البيانات إلى القناة المناسبة بناءً على الوضع، ثم استدعِMonitorواطبع نتيجته.بالنسبة لوضع
check: اقرأ اسم الخدمة وحالتها، بالإضافة إلى علامة (sendأوnosend) تشير إلى ما إذا كان سيتم إرسال البيانات قبل الفحص. قم بإنشاء قناة مخزنة (buffered channel) بسعة 1. إذا كانت العلامة هيsend، فأرسل تحديثاً إلى القناة قبل استدعاءCheckStatus. اطبع نتيجةCheckStatus.
سيتم توفير المدخلات التالية:
- السطر 1: الوضع (
primary، أوbackup، أوstop، أوcheck) - السطر 2: اسم الخدمة
- السطر 3: حالة الخدمة
- السطر 4 (فقط لوضع
check):sendأوnosend
على سبيل المثال، إذا كان المعطى:
primary
Database
healthyيجب أن يكون مخرجك:
Primary: Database is healthyوإذا كان المعطى:
check
Cache
active
nosendيجب أن يكون مخرجك:
No updates pendingورقة مرجعية
تسمح عبارة select لـ goroutine بالانتظار على عمليات قنوات (channels) متعددة في وقت واحد. وهي تعمل مثل عبارة switch، ولكن كل حالة (case) تتضمن إرسالاً أو استقبالاً عبر القناة:
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}عندما تكون قنوات متعددة جاهزة، تختار select واحدة بشكل عشوائي. بدون select، فإن الاستقبال من قناة واحدة سيؤدي إلى الحظر (block) وربما فقدان البيانات في قناة أخرى.
تجعل حالة default عبارة select غير حاظرة (non-blocking) - فهي تُنفذ فوراً إذا لم تكن هناك قنوات جاهزة:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
default:
fmt.Println("No message available")
}يجمع نمط شائع بين select والمهلات الزمنية (timeouts) باستخدام time.After، والتي تعيد قناة تستقبل قيمة بعد المدة المحددة:
select {
case result := <-ch:
fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout!")
}هذا يمنع برنامجك من الانتظار للأبد عندما قد لا تكتمل عملية القناة أبداً.
جرّب بنفسك
package main
import (
"fmt"
)
func main() {
var mode string
fmt.Scanln(&mode)
var serviceName string
fmt.Scanln(&serviceName)
var serviceStatus string
fmt.Scanln(&serviceStatus)
// TODO: التعامل مع الأوضاع المختلفة: "primary"، "backup"، "stop"، "check"
// للأوضاع "primary"، "backup"، "stop":
// - إنشاء ثلاث قنوات: primary، backup، done
// - إطلاق goroutine يرسل البيانات إلى القناة المناسبة
// - استدعاء Monitor وطباعة النتيجة
// للوضع "check":
// - قراءة العلم الإضافي ("send" أو "nosend")
// - إنشاء قناة مخزنة (buffered channel) بسعة 1
// - إذا كان العلم هو "send"، يتم إرسال تحديث إلى القناة
// - استدعاء CheckStatus وطباعة النتيجة
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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