لماذا Go هي اللغة المثالية لقوائم الانتظار والمهام الخلفية؟
\n\nإذا سبق لك أن كتبت باك إند باستخدام Python أو Node.js، فبالتأكيد واجهت هذه المشكلة: التطبيق يعالج طلبًا، وفي نفس الوقت يحتاج إلى إرسال بريد إلكتروني، أو إنشاء تقرير، أو معالجة ملف تم تحميله. إذا قمت بذلك بشكل متزامن (Synchronous) — سينتظر المستخدم. أما إذا استخدمت خيطًا منفصلاً — فستبدأ مشاكل الأقفال (Locks) وسباق البيانات (Race Conditions).
\n\nGo تحل هذه المشكلة بأناقة وبشكل أصلي. Goroutines و Channels هما آليتان مدمجتان للتوازي (Concurrency) تجعلان تطوير قوائم الانتظار والمهام الخلفية في Go بسيطًا وآمنًا. في هذا المقال، سنشرح كيفية تنظيم المعالجة الخلفية باستخدام المكتبة القياسية وأيضًا الأدوات الشائعة مثل RabbitMQ. ستشاهد أمثلة كود عملية يمكنك تطبيقها فورًا في مشروعك.
\n\nالأساسيات: Goroutines و Channels
\n\nقبل أن نتعمق في قوائم الانتظار، دعنا نراجع الأساسيات. Goroutine هي خيط تنفيذ خفيف الوزن يتم تشغيله باستخدام الكلمة المفتاحية go. Channel هو "أنبوب" مكتوب (Typed) تتواصل من خلاله الـ Goroutines مع بعضها البعض.
مثال بسيط: إرسال بريد إلكتروني بشكل غير متزامن
\n\npackage main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc sendEmail(email string) {\n time.Sleep(2 * time.Second) // محاكاة عملية طويلة\n fmt.Println("تم إرسال البريد الإلكتروني إلى", email)\n}\n\nfunc main() {\n fmt.Println("البدء")\n go sendEmail("user@example.com") // تشغيل في الخلفية\n fmt.Println("متابعة المعالجة...")\n time.Sleep(3 * time.Second) // انتظار انتهاء الـ Goroutine\n}\n\nهنا، يتم تنفيذ sendEmail في الخلفية، بينما لا يتم حظر التدفق الرئيسي. ولكن ماذا لو كنت بحاجة إلى معالجة 1000 بريد إلكتروني؟ تشغيل 1000 Goroutine ليس فكرة جيدة: ستبدأ في التنافس على الموارد. هنا يأتي دور الـ Channels ونمط Worker Pool.
نمط Worker Pool في Go
\n\nWorker Pool هو نهج كلاسيكي لتنظيم قوائم الانتظار والمهام الخلفية في Go. لديك عدد ثابت من العمال (Goroutines) يستمعون إلى قناة واحدة تحتوي على المهام. تأتي المهام من التدفق الرئيسي أو من معالج HTTP.
\n\nتطبيق Worker Pool
\n\npackage main\n\nimport (\n "fmt"\n "sync"\n "time"\n)\n\ntype Task struct {\n ID int\n Data string\n}\n\nfunc worker(id int, tasks <-chan Task, wg *sync.WaitGroup) {\n defer wg.Done()\n for task := range tasks {\n fmt.Printf("العامل %d يعالج المهمة %d: %s\\n", id, task.ID, task.Data)\n time.Sleep(1 * time.Second) // محاكاة العمل\n }\n}\n\nfunc main() {\n const numWorkers = 3\n tasks := make(chan Task, 10) // قناة مخزنة (Buffered Channel)\n\n var wg sync.WaitGroup\n\n // تشغيل العمال\n for i := 1; i <= numWorkers; i++ {\n wg.Add(1)\n go worker(i, tasks, &wg)\n }\n\n // إرسال المهام\n for i := 1; i <= 10; i++ {\n tasks <- Task{ID: i, Data: fmt.Sprintf("المهمة %d", i)}\n }\n close(tasks) // إغلاق القناة لإنهاء العمال\n\n wg.Wait()\n fmt.Println("تم إنجاز جميع المهام")\n}\n\nهذا الكود ينشئ 3 عمال يعالجون 10 مهام. القناة المخزنة (Buffered Channel) تسمح بإرسال المهام دون حظر طالما أن المخزن المؤقت لم يمتلئ. هذا النمط مثالي لـ المهام الخلفية في Golang في تطبيقات الويب: تستقبل الطلب، تضع المهمة في القناة، وترد على المستخدم فورًا.
\n\nقوائم الانتظار في Go باستخدام RabbitMQ
\n\nبالنسبة للأنظمة الموزعة، القنوات الموجودة في الذاكرة ليست كافية — فأنت بحاجة إلى مخزن خارجي لقوائم الانتظار. RabbitMQ هو أحد أشهر وسطاء الرسائل. دعنا نرى كيفية ربطه بتطبيق Go.
\n\nالتثبيت والاتصال
\n\nأولاً، قم بتثبيت مكتبة amqp:
go get github.com/rabbitmq/amqp091-go\n\n