قوائم المهام في Go: دليل عملي

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

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

ابدأ الدورة

مقدمة: لماذا نحتاج إلى قوائم المهام في Go؟

\n

في التطوير الحديث، غالبًا ما تنشأ الحاجة إلى معالجة المهام بشكل غير متزامن دون حظر التدفق الرئيسي للتطبيق. قوائم المهام (task queues) هي نمط كلاسيكي يسمح بتوزيع العمل بين عدة معالجات (عمال)، وإدارة الحمل، وضمان تحمل الأخطاء.

\n

توفر لغة Go (Golang) آليات مدمجة لتنفيذ قوائم المهام: الغوروتينات (goroutines) و القنوات (channels). في هذه المقالة، سنقوم عمليًا ببناء قائمة مهام موثوقة باستخدام المكتبة القياسية لـ Go، دون اللجوء إلى وسطاء الرسائل الخارجيين (على الرغم من أن RabbitMQ أو Redis قد تكون مفيدة في بيئة الإنتاج).

\n\n

الجزء الرئيسي

\n\n

1. النمط الأساسي لمجموعة العمال (Worker Pool)

\n

مجموعة العمال هي مجموعة من عدد ثابت من الغوروتينات (العمال) التي تستمع إلى قناة واحدة وتعالج المهام الواردة. يسمح هذا بالتحكم في درجة التوازي وتجنب التحميل الزائد على النظام.

\n

لننظر إلى مثال بسيط: نقوم بإنشاء مجموعة من 3 عمال تقوم بمعالجة الأعداد الصحيحة (محاكاة للعمل).

\n
package main\n\nimport (\n    "fmt"\n    "sync"\n    "time"\n)\n\nfunc worker(id int, jobs <-chan int, wg *sync.WaitGroup) {\n    defer wg.Done()\n    for job := range jobs {\n        fmt.Printf("العامل %d بدأ المهمة %d\\n", id, job)\n        time.Sleep(time.Second) // محاكاة العمل\n        fmt.Printf("العامل %d أكمل المهمة %d\\n", id, job)\n    }\n}\n\nfunc main() {\n    const numJobs = 5\n    const numWorkers = 3\n\n    jobs := make(chan int, numJobs)\n    var wg sync.WaitGroup\n\n    // تشغيل العمال\n    for w := 1; w <= numWorkers; w++ {\n        wg.Add(1)\n        go worker(w, jobs, &wg)\n    }\n\n    // إرسال المهام\n    for j := 1; j <= numJobs; j++ {\n        jobs <- j\n    }\n    close(jobs) // إغلاق القناة لإنهاء العمال\n\n    wg.Wait()\n    fmt.Println("تم تنفيذ جميع المهام")\n}
\n

تحليل الكود:

\n
    \n
  • jobs — قناة مخزنة مؤقتًا نرسل إليها المهام.
  • \n
  • worker — دالة تقرأ من القناة وتعالج كل مهمة.
  • \n
  • sync.WaitGroup — تنتظر اكتمال جميع العمال.
  • \n
  • بعد إرسال جميع المهام، يتم إغلاق القناة (close(jobs))، مما يؤدي إلى إنهاء حلقة for range في العمال.
  • \n
\n

هذا النمط هو الأساس للحلول الأكثر تعقيدًا.

\n\n

2. قائمة ذات أولويات ومهلات زمنية

\n

في التطبيقات الحقيقية، قد يكون للمهام أولويات مختلفة أو قد تتطلب التنفيذ خلال فترة زمنية محددة. سنقوم بتنفيذ قائمة حيث تتم معالجة المهام ذات الأولوية أولاً، بالإضافة إلى إضافة سياق بمهلة زمنية لإلغاء المهام الطويلة.

\n
package main\n\nimport (\n    "context"\n    "fmt"\n    "time"\n)\n\ntype Task struct {\n    ID       int\n    Priority int // كلما كان الرقم أصغر، زادت الأولوية\n    Data     string\n}\n\nfunc processTask(ctx context.Context, task Task) error {\n    select {\n    case <-ctx.Done():\n        return ctx.Err()\n    case <-time.After(time.Duration(task.Priority) * 500 * time.Millisecond):\n        fmt.Printf("تمت معالجة المهمة %d: %s (الأولوية %d)\\n", task.ID, task.Data, task.Priority)\n        return nil\n    }\n}\n\nfunc main() {\n    tasks := []Task{\n        {ID: 1, Priority: 3, Data: "أولوية منخفضة"},\n        {ID: 2, Priority: 1, Data: "أولوية عالية"},\n        {ID: 3, Priority: 2, Data: "أولوية متوسطة"},\n    }\n\n    // ترتيب حسب الأولوية (الأعلى أولاً)\n    // في المشروع الحقيقي، استخدم الترتيب أو الكومة (container/heap)\n    for i := 0; i < len(tasks); i++ {\n        for j := i + 1; j < len(tasks); j++ {\n            if tasks[j].Priority < tasks[i].Priority {\n                tasks[i], tasks[j] = tasks[j], tasks[i]\n            }\n                

المدونات

توصيات الكتب