42 / 68

Ishchilar puli (Worker Pool)

Nima uchun kerak?

Tasavvur qiling: sizda 1000 ta vazifa bor (masalan, 1000 ta faylni qayta ishlash). Ularning har biri uchun alohida-alohida goroutine ochish (go process(1), go process(2), ... go process(1000)) texnik jihatdan mumkin — goroutinelar juda yengil vaznli. Lekin agar har bir vazifa katta hajmdagi resurs (masalan, tarmoq ulanishi yoki ma'lumotlar bazasi bilan bog'lanish) talab qilsa, bir vaqtning o'zida 1000 ta ulanish ochish tizimni tiz cho'ktirib qo'yishi mumkin.

Yechim — ishchilar puli (worker pool): ma'lum sondagi (masalan, 3 ta) doimiy goroutine ("workerlar") ishga tushiriladi, va ular vazifalarni kanal orqali navbat bilan olib bajaradi. Bu — goroutine, channel va WaitGroup'ni birlashtiruvchi Godagi eng mashhur amaliy patternlardan biri.


1. Patternning uch qismi

  1. Vazifalar kanali (jobs) — bajarilishi kerak bo'lgan vazifalar shu yerga "navbatga qo'yiladi".
  2. Workerlar — kanaldan navbat bilan vazifa olib, uni bajaradigan bir nechta goroutine.
  3. Natijalar kanali (results) — har bir worker o'z natijasini shu yerga yozadi.

2. To'liq misol: sonlarning kvadratini hisoblash

example.go
package main

import (
    "fmt"
    "sync"
)

// worker — jobs kanalidan vazifa kelguncha kutadi, uni bajarib, results ga yozadi
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()

    // jobs yopilguncha undan vazifalarni birin-ketin olaveradi
    for j := range jobs {
        fmt.Printf("Worker %d %d-ishni bajarmoqda\n", id, j)
        results <- j * j
    }
}

func main() {
    const numWorkers = 3
    const numJobs = 9

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

    var wg sync.WaitGroup

    // 3 ta workerni oldindan ishga tushiramiz
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // 9 ta vazifani navbatga qo'yamiz
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs) // boshqa vazifa yo'qligini bildiramiz

    wg.Wait()      // barcha workerlar tugashini kutamiz
    close(results) // endi natijalar kanalini ham yopamiz

    sum := 0
    for r := range results {
        sum += r
    }

    fmt.Println("Yig'indi:", sum)
}

Ehtimoliy natija (workerlar orasidagi tartib har safar farq qiladi, lekin yig'indi doim bir xil):

kod
Worker 1 2-ishni bajarmoqda
Worker 3 1-ishni bajarmoqda
Worker 2 3-ishni bajarmoqda
...
Yig'indi: 285

1² + 2² + ... + 9² = 285 — bu qiymat har doim bir xil chiqadi, chunki barcha 9 ta vazifa, qaysi worker tomonidan bajarilishidan qat'i nazar, albatta bir marta hisoblanadi. Faqat konsolga qaysi worker qachon chop etishi (tartib) — bu ixtiyoriy.


3. Nega bu pattern muhim?

Muammo (workersiz)Yechim (worker pool bilan)
1000 ta vazifa uchun 1000 ta bir vaqtdagi tarmoq/BAZA ulanishiFaqat numWorkers ta bir vaqtdagi ulanish
Resurslarni nazoratsiz sarflashBir vaqtda ishlaydigan vazifalar sonini aniq cheklash
Sekin/tez vazifalarni muvozanatlash qiyinTez tugagan worker keyingi vazifani darhol oladi (avtomatik yuklama balansi)

[!NOTE] numWorkers qiymatini tanlashda odatda vazifa turiga qarab tanlanadi: agar vazifa CPU bilan bog'liq bo'lsa (og'ir hisob-kitob), odatda runtime.NumCPU() atrofida tanlanadi; agar vazifa I/O bilan bog'liq bo'lsa (tarmoq, fayl, baza — ko'p vaqtini kutishga sarflaydi), CPU yadrolari sonidan ancha ko'proq worker (masalan, 50-100) tanlash ham mumkin, chunki ular asosan "kutib" turadi, protsessorni band qilmaydi.


Xulosa

QismVazifasi
jobs kanaliBajarilishi kerak bo'lgan vazifalarni saqlaydi
results kanaliHar bir workerning natijasini yig'adi
worker() funksiyasijobsdan navbat bilan oladi, resultsga yozadi
sync.WaitGroupBarcha workerlar tugashini kutish uchun
close(jobs)"Boshqa vazifa yo'q" signali

Bu pattern — goroutine, channel va WaitGroup'ni birgalikda qanday ishlatish mumkinligining eng yaxshi amaliy namunasi bo'lib, ko'plab real Go loyihalarida (fayllarni qayta ishlash, ko'plab API so'rovlarini yuborish, ma'lumotlar bazasini import qilish) aynan shu tarzda qo'llaniladi.