Go بالأمثلة: أحواض العمال (Worker Pools)

في هذا المثال سنلقي نظرة على كيفية تنفيذ حوض عمال (worker pool) باستخدام الـ goroutines والقنوات.

package main

import (
    "fmt"
    "time"
)

هذا هو العامل، والذي سنقوم بتشغيل عدة نسخ متوازية منه. سيستقبل هؤلاء العمال المهام على قناة jobs ويرسلون النتائج المقابلة على results. سنقوم بالانتظار ثانية واحدة لكل مهمة لمحاكاة مهمة مكلفة برمجياً.

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("العامل", id, "بدأ المهمة", j)
        time.Sleep(time.Second)
        fmt.Println("العامل", id, "أنهى المهمة", j)
        results <- j * 2
    }
}

func main() {

لكي نستخدم حوض العمال الخاص بنا، نحتاج لإرسال المهام لهم وجمع نتائجهم. نقوم بإنشاء قناتين لهذا الغرض.

    const numJobs = 5
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

هذا يبدأ 3 عمال، والذين سيكونون معيقين في البداية لعدم وجود وظائف بعد.

    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

هنا نرسل 5 jobs ثم نغلق تلك القناة للإشارة إلى أن هذا كل العمل الذي لدينا.

    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

أخيراً نجمع كل نتائج العمل. هذا يضمن أيضاً أن الـ goroutines الخاصة بالعمال قد انتهت. هناك طريقة بديلة لانتظار عدة goroutines وهي استخدام ‘WaitGroup’.

    for a := 1; a <= numJobs; a++ {
        <-results
    }
}

يظهر برنامجنا المشغل تنفيذ المهام الـ 5 بواسطة عمال مختلفين. يستغرق البرنامج حوالي ثانيتين فقط على الرغم من قيامه بعمل يستغرق إجمالاً حوالي 5 ثوانٍ، وذلك لوجود 3 عمال يعملون بالتوازي.

تشغيل البرنامج:

$ time go run worker-pools.go 
العامل 1 بدأ المهمة 1
العامل 2 بدأ المهمة 2
العامل 3 بدأ المهمة 3
العامل 1 أنهى المهمة 1
العامل 1 بدأ المهمة 4
العامل 2 أنهى المهمة 2
العامل 2 بدأ المهمة 5
العامل 3 أنهى المهمة 3
العامل 1 أنهى المهمة 4
العامل 2 أنهى المهمة 5

real    0m2.358s

المثال التالي: مجموعات الانتظار (WaitGroups).