Dasturlash

Go dasturida konkurrentlik: Goroutine va kanallarni chuqur tahlil

27-sentabr, 2026, 12:040 ko'rish5 daqiqa o'qish
Go dasturida konkurrentlik: Goroutine va kanallarni chuqur tahlil

Go dasturlash tili o'zining yengil vaznli goroutine va kuchli kanal mexanizmlari bilan konkurrentlikni soddalashtiradi. Ushbu maqolada, Anton Z. ning "Go Concurrency Distilled" mini‑kitobidan olingan asosiy tushunchalar tahlil qilinadi, amaliy kod misollari bilan mustahkamlanadi va boshqa paralel texnologiyalar bilan solishtiriladi.

Konkurrentlikka kirish: nega Go tanlanadi?

Ko'p dasturchilar paralel ishlashni amalga oshirishda OS iplarini (thread) boshqarishning murakkabligi bilan duch keladi. Go esa bu jarayonni runtime darajasida avtomatlashtiradi: goroutine‑lar yengil vaznli, bir necha yuz yoki minglab sonini yaratish mumkin, bu esa resurslarni tejashga yordam beradi. Go runtime iplarni CPU yadrolariga taqsimlaydi, shuning uchun dasturchi qo'lda iplarni yaratish yoki sinxronizatsiya qilish bilan shug'ullanmaydi.

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        fmt.Println("worker 1")
    }()
    go func() {
        defer wg.Done()
        fmt.Println("worker 2")
    }()
    wg.Wait()
}

Goroutine: yengil vaznli funksional iplar

Goroutine‑ni yaratish uchun go kalit so'zi ishlatiladi. Quyidagi misolda ikkita goroutine worker 1 va worker 2 ni bajaradi, sync.WaitGroup yordamida asosiy funksiyaning tugashini kutadi:

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        fmt.Println("worker 1")
    }()
    go func() {
        defer wg.Done()
        fmt.Println("worker 2")
    }()
    wg.Wait()
}

Bu kodda WaitGroup ichidagi hisoblagich Add bilan oshiriladi, Done bilan kamayadi, Wait esa hisoblagich noldan bo'lgunga qadar bloklaydi. Natijada, asosiy goroutine (main) barcha ishchi goroutine‑lar tugaguncha kutadi.

worker 2
worker 1

Kanallar: goroutine‑lar orasida ma'lumot almashish

Kanallar (chan) – bu goroutine‑lar o'rtasida ma'lumot uzatish uchun maxsus obyektlar. Ular sinxron (unbuffered) yoki buferli (buffered) bo'lishi mumkin. Sinxron kanalda jo'natuvchi ma'lumotni yozganda bloklanadi, qabul qiluvchi o'qiguncha kutadi. Bu xususiyat ma'lumot oqimini aniq nazorat qilishga imkon beradi.

Quyidagi kod sinxron kanal orqali ping xabarini uzatadi:

func main() {
    var wg sync.WaitGroup
    wg.Go(func() {
        fmt.Println("worker 1")
    })
    wg.Go(func() {
        fmt.Println("worker 2")
    })
    wg.Wait()
}
func main() {
    messages := make(chan string)
    go func() { messages <- "ping" }()
    msg := <-messages
    fmt.Println(msg)
}

Natija ping bo'ladi. Kanalni yopish (close) o'quvchilarga ma'lumotlar oxiri kelganini bildiradi. O'qish paytida ikkinchi qiymat (, ok) kanal holatini ko'rsatadi – true ochiq, false yopiq.

Buferli kanallar va ularning afzalliklari

Buferli kanal ma'lumotlarni ichki FIFO (first‑in‑first‑out) navbatda saqlaydi. Bufer bo'sh bo'lsa, yozish bloklanadi; bufer to'liq bo'lsa, o'qish bloklanadi. Bu xususiyat ko'p ma'lumotni bir vaqtning o'zida uzatish kerak bo'lganda samarali.

worker 2
worker 1
stream := make(chan int, 3)
stream <- 11
stream <- 12
stream <- 13
fmt.Println(<-stream) // 11
fmt.Println(<-stream) // 12

Bufer o'lchamini len va cap funksiyalari bilan tekshirish mumkin.

Yo'naltirilgan kanallar: xavfsizlikni oshirish

Funksiya parametrlarida kanal yo'nalishini belgilash (chan<- – faqat yozish, <-chan – faqat o'qish) xatoliklarni oldini oladi. Misol:

func main() {
    messages := make(chan string)

    go func() { messages <- "ping" }()

    msg := <-messages
    fmt.Println(msg)
}
func sendOnly(ch chan<- int) { ch <- 42 }
func receiveOnly(ch <-chan int) { fmt.Println(<-ch) }

stream := make(chan int)
go sendOnly(stream)
receiveOnly(stream)

Bu kodda sendOnly faqat yozadi, receiveOnly faqat o'qiydi, shuning uchun noto'g'ri yo'naltirish xatolari kompilyatsiya vaqtida aniqlanadi.

Sync paketining boshqa konstruktsiyalari

Go sync paketida Mutex, RWMutex, Once, Pool, Atomic kabi sinxronizatsiya vositalari mavjud. Masalan, Mutex bir vaqtning o'zida faqat bitta goroutine ma'lumotga kirishini ta'minlaydi, Once esa bir marta bajariladigan kod blokini yaratadi.

func generate(start, stop int) chan int {
    out := make(chan int)
    go func() {
        for i := start; i < stop; i++ {
            out <- i
        }
    }()
    return out
}

Quyidagi misolda sync.Once yordamida bir marta boshlang'ich sozlamalar amalga oshiriladi:

var once sync.Once
func initConfig() { fmt.Println("config initialized") }

func main() {
    for i := 0; i < 3; i++ {
        go func() { once.Do(initConfig) }()
    }
    time.Sleep(time.Second)
}

Natija faqat bitta config initialized xabarini chiqaradi.

func generate(start, stop int) chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for i := start; i < stop; i++ {
            out <- i
        }
    }()
    return out
}

Go konkurrentligini boshqa texnologiyalar bilan solishtirish

OS iplari (C/C++, Java) bilan solishtirganda, goroutine‑lar yengil vaznli va avtomatik rejalashtiriladi, shuning uchun millionlab iplarni yaratish amaliy emas. Java’da ExecutorService yoki CompletableFuture kabi abstraksiyalar mavjud, lekin ular ham alohida thread pool boshqaruvini talab qiladi. Rust’da async/await va tokio runtime’lari mavjud, ammo ularning sintaksisi va ekotizimi Go‑ga qaraganda murakkabroq.

Shuningdek, Go kanallarini select operatori bilan birlashtirish mumkin, bu esa bir nechta kanalga bir vaqtning o'zida kuzatish imkonini beradi – bu xususiyat boshqa tillarda maxsus kutubxonalar orqali amalga oshiriladi.

func main() {
    in := generate(5, 10)
    for {
        num, ok := <-in
        if !ok {
            break
        }
        fmt.Print(num, " ")
    }
}

Cheklovlar va ochiq savollar

  • Goroutine‑lar hamma holatda ham samarali bo‘la olmaydi – CPU‑intensiv vazifalar uchun maxsus worker pool yoki Cgo orqali native kodga murojaat qilish zarur.
  • Kanallarni noto‘g‘ri yopish yoki yopilmagan kanaldan o‘qish deadlock (tutilish) holatiga olib kelishi mumkin.
  • Data race (ma'lumotlar o‘rtasidagi to‘qnashuv) aniqlash uchun go test -race flagi ishlatiladi, lekin ba'zi murakkab holatlarda bu vosita ham yetarli bo‘lmasligi mumkin.

Bu muammolarni hal qilish uchun kodni sinchkovlik bilan tahlil qilish, testlar yozish va profiling vositalaridan (pprof, trace) foydalanish tavsiya etiladi.

Amaliy ta'sir: kimga va qanday foyda keltiradi?

Go konkurrentlik konseptsiyasi server‑tomon dasturlari, mikroservislar, real‑vaqt ma'lumot oqimlari va yuqori yuklamali web ilovalar uchun ayniqsa foydalidir. Goroutine‑lar yordamida har bir so‘rovni alohida ipda bajarish, resurslarni samarali taqsimlash va javob vaqtini qisqartirish mumkin. Shuningdek, kanallar orqali pipeline (ma'lumot quvur) arxitekturasini yaratish, ma'lumotni bosqichma‑bosqich qayta ishlashni soddalashtiradi.

Go‑ning bu yengil va kuchli konkurrentlik modeli, dasturchilarga murakkab paralel algoritmlarni sodda kod bilan ifodalash imkonini beradi, bu esa ishlab chiqish vaqtini qisqartiradi va kodning o‘qilishi ham yaxshilanadi.

Manba: Go Concurrency Distilled

Asl manba: antonz.org

Manba: Hacker News
#go #concurrency #goroutine #channel #sync
Telegram da muhokama qilish