مهام الخلفية وقوائم الانتظار في Go: goroutines، channels وأنماط متقدمة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا Go هي اللغة المثالية لقوائم الانتظار والمهام الخلفية؟

\n\n

إذا سبق لك أن كتبت باك إند باستخدام Python أو Node.js، فبالتأكيد واجهت هذه المشكلة: التطبيق يعالج طلبًا، وفي نفس الوقت يحتاج إلى إرسال بريد إلكتروني، أو إنشاء تقرير، أو معالجة ملف تم تحميله. إذا قمت بذلك بشكل متزامن (Synchronous) — سينتظر المستخدم. أما إذا استخدمت خيطًا منفصلاً — فستبدأ مشاكل الأقفال (Locks) وسباق البيانات (Race Conditions).

\n\n

Go تحل هذه المشكلة بأناقة وبشكل أصلي. Goroutines و Channels هما آليتان مدمجتان للتوازي (Concurrency) تجعلان تطوير قوائم الانتظار والمهام الخلفية في Go بسيطًا وآمنًا. في هذا المقال، سنشرح كيفية تنظيم المعالجة الخلفية باستخدام المكتبة القياسية وأيضًا الأدوات الشائعة مثل RabbitMQ. ستشاهد أمثلة كود عملية يمكنك تطبيقها فورًا في مشروعك.

\n\n

الأساسيات: Goroutines و Channels

\n\n

قبل أن نتعمق في قوائم الانتظار، دعنا نراجع الأساسيات. Goroutine هي خيط تنفيذ خفيف الوزن يتم تشغيله باستخدام الكلمة المفتاحية go. Channel هو "أنبوب" مكتوب (Typed) تتواصل من خلاله الـ Goroutines مع بعضها البعض.

\n\n

مثال بسيط: إرسال بريد إلكتروني بشكل غير متزامن

\n\n
package 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.

\n\n

نمط Worker Pool في Go

\n\n

Worker Pool هو نهج كلاسيكي لتنظيم قوائم الانتظار والمهام الخلفية في Go. لديك عدد ثابت من العمال (Goroutines) يستمعون إلى قناة واحدة تحتوي على المهام. تأتي المهام من التدفق الرئيسي أو من معالج HTTP.

\n\n

تطبيق Worker Pool

\n\n
package 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:

\n\n
go get github.com/rabbitmq/amqp091-go
\n\n

المنتج (Producer) - إرسال المهام

\

المدونات

توصيات الكتب