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.
jobs) — bajarilishi kerak bo'lgan vazifalar shu yerga "navbatga qo'yiladi".results) — har bir worker o'z natijasini shu yerga yozadi.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):
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.
| Muammo (workersiz) | Yechim (worker pool bilan) |
|---|---|
| 1000 ta vazifa uchun 1000 ta bir vaqtdagi tarmoq/BAZA ulanishi | Faqat numWorkers ta bir vaqtdagi ulanish |
| Resurslarni nazoratsiz sarflash | Bir vaqtda ishlaydigan vazifalar sonini aniq cheklash |
| Sekin/tez vazifalarni muvozanatlash qiyin | Tez tugagan worker keyingi vazifani darhol oladi (avtomatik yuklama balansi) |
[!NOTE]
numWorkersqiymatini tanlashda odatda vazifa turiga qarab tanlanadi: agar vazifa CPU bilan bog'liq bo'lsa (og'ir hisob-kitob), odatdaruntime.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.
| Qism | Vazifasi |
|---|---|
jobs kanali | Bajarilishi kerak bo'lgan vazifalarni saqlaydi |
results kanali | Har bir workerning natijasini yig'adi |
worker() funksiyasi | jobsdan navbat bilan oladi, resultsga yozadi |
sync.WaitGroup | Barcha 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.