Tarkibga o'tish

Bufersiz, buferli va yo‘nalishli channellar

Go’da channel bir xil qiymat uzatish modeliga ega, ammo uning sig‘imi va ruxsat etilgan yo‘nalishi turlicha bo‘lishi mumkin. Bufersiz channel yuboruvchi bilan qabul qiluvchini bevosita uchrashtiradi. Buferli channel ular orasiga cheklangan navbat qo‘yadi. Yo‘nalishli channel esa funksiya qaysi amalni bajarishi mumkinligini tur darajasida cheklaydi.

Bu farqlar faqat sintaksis emas. Ular bloklanish, yuklamani boshqarish va API dizayniga ta’sir qiladi.

Bufersiz channel

Sig‘im ko‘rsatilmasa yoki 0 berilsa, bufersiz channel yaratiladi:

ch := make(chan int)
// Quyidagi yozuv ham xuddi shunday:
other := make(chan int, 0)

Bufersiz channel qiymatni saqlash uchun navbatga ega emas. Send mos receive tayyor bo‘lguncha, receive esa mos send tayyor bo‘lguncha bloklanadi. Qiymat almashilganda ikki goroutine sinxronlashadi.

Misol: ish tugaganini signal qilish

main.go
package main

import "fmt"

func prepare(done chan<- struct{}) {
    fmt.Println("Ma’lumot tayyorlandi")
    done <- struct{}{}
}

func main() {
    done := make(chan struct{})

    go prepare(done)
    <-done

    fmt.Println("Dastur davom etdi")
}

Natija:

Ma’lumot tayyorlandi
Dastur davom etdi

struct{}{} foydali ma’lumot saqlamaydigan signal qiymati. main receive amalida prepare signal yuborguncha kutadi. Senddan oldingi Println bajarilib bo‘lgach signal yuboriladi, shu sabab yakuniy satr keyin chiqadi.

Bufersiz channel ishni topshirish, javobni kutish yoki ikki goroutine o‘rtasida aniq sinxronizatsiya nuqtasi yaratish uchun mos.

Qabul qiluvchisiz send

main.go
1
2
3
4
5
6
package main

func main() {
    ch := make(chan int)
    ch <- 10
}

Bu dastur deadlock bilan tugaydi. main qiymatni qabul qiladigan goroutine bo‘lmagani uchun sendda bloklanadi.

Buferli channel

Musbat sig‘im berilsa, buferli channel yaratiladi:

ch := make(chan int, 3)

Bu channel uchta qiymatni navbatda saqlay oladi:

  • buferda bo‘sh joy bo‘lsa, send qabul qiluvchini kutmasdan tugashi mumkin;
  • bufer to‘lsa, keyingi send joy bo‘shaguncha bloklanadi;
  • buferda qiymat bo‘lsa, receive uni darhol olishi mumkin;
  • bufer bo‘sh bo‘lsa, receive yangi qiymat kelguncha bloklanadi.

Eng kichik misol

main.go
package main

import "fmt"

func main() {
    ch := make(chan int, 2)

    ch <- 10
    ch <- 20

    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

Natija:

10
20

Ikki send qabul qiluvchisiz tugaydi, chunki bufer sig‘imi 2. Channel qiymatlarni yuborilgan tartibda saqlaydi. Bitta channel ichidagi qiymatlar FIFO tartibida qabul qilinadi.

FIFO turli yuboruvchi goroutinelarning qaysi biri birinchi send qilishini kafolatlamaydi. Scheduler sabab sendlar channelga boshqa tartibda yetib kelishi mumkin. Channel faqat qabul qilish tartibi muvaffaqiyatli sendlar tartibiga mos bo‘lishini ta’minlaydi.

Uchinchi qiymatni qabul qilishdan oldin yuborishga urinish mainni bloklab qo‘yardi:

// ch := make(chan int, 2)
// ch <- 10
// ch <- 20
// ch <- 30 // Bufer to‘la va qabul qiluvchi yo‘q: deadlock.

Amaliy misol: cheklangan navbat

Quyidagi dastur ishlab chiqaruvchi va iste’molchini buferli channel orqali bog‘laydi.

main.go
package main

import "fmt"

func produce(jobs chan<- int) {
    for job := 1; job <= 5; job++ {
        jobs <- job
    }
    close(jobs)
}

func main() {
    jobs := make(chan int, 2)

    go produce(jobs)

    for job := range jobs {
        fmt.Println("Qabul qilindi:", job)
    }
}

Natija:

Qabul qilindi: 1
Qabul qilindi: 2
Qabul qilindi: 3
Qabul qilindi: 4
Qabul qilindi: 5

Ishlab chiqaruvchi buferda joy boricha oldinga o‘tishi mumkin. Navbat to‘lganda iste’molchi qiymat olguncha bloklanadi. produce barcha ishlarni yuborgach channelni yopadi. range buferdagi qolgan qiymatlarni ham olib, keyin tugaydi.

Bufer ishlab chiqaruvchi va iste’molchi tezligini vaqtincha ajratadi. Iste’molchi doimiy ravishda sekin bo‘lsa, bufer oxir-oqibat to‘ladi va ishlab chiqaruvchi bloklanadi. Bu teskari bosim (backpressure) hosil qiladi va cheksiz xotira o‘sishining oldini oladi.

Sig‘imni qanday tanlash kerak?

Bufer hajmi tasodifiy katta son bo‘lmasligi kerak. U navbatda qancha tugallanmagan ishga ruxsat berilishini ifodalaydi.

Sig‘imni tanlashda quyidagilarni hisobga oling:

  • iste’molchi bir paytda qancha ishni qayta ishlay oladi;
  • vaqtinchalik yuklama sakrashi qancha bo‘lishi mumkin;
  • har bir qiymat qancha xotira egallaydi;
  • ortiqcha navbat natijaning eskirishiga olib keladimi;
  • tizim sekinlashganda yuboruvchi bloklanishi kerakmi.

Juda kichik bufer keragidan ortiq bloklanish keltirishi mumkin. Juda katta bufer yuklama muammosini kechiktiradi, xotira sarfini oshiradi va eski ishlarni navbatda ushlab turadi. Avval kerakli semantikani belgilang, so‘ng o‘lchov va yuklama testi bilan sig‘imni tekshiring.

len va cap

cap(ch) channel buferining sig‘imini qaytaradi. Bufersiz channel uchun u 0. len(ch) esa hozir buferda turgan qiymatlar sonini beradi.

main.go
package main

import "fmt"

func main() {
    ch := make(chan string, 3)
    ch <- "bir"
    ch <- "ikki"

    fmt.Println("Uzunlik:", len(ch))
    fmt.Println("Sig‘im:", cap(ch))
}

Natija:

Uzunlik: 2
Sig‘im: 3

Concurrent dasturda len(ch) faqat o‘sha ondagi holatni ko‘rsatadi. Tekshiruvdan keyin boshqa goroutine qiymat yuborishi yoki olishi mumkin. Shu sabab if len(ch) < cap(ch) tekshiruvi keyingi send bloklanmasligini kafolatlamaydi.

Odatda dastur mantig‘ini len(ch)ga bog‘lamang. Bloklamaydigan send yoki receive kerak bo‘lsa, select va default ishlatiladi. Bu keyingi darsda ko‘rib chiqiladi.

Yo‘nalishli channel turlari

Oddiy chan T ikki tomonlama channel: unga qiymat yuborish ham, undan qabul qilish ham mumkin. Funksiya parametrida ruxsatni toraytirish mumkin:

func send(out chan<- int) {
    out <- 10
}

func receive(in <-chan int) int {
    return <-in
}
  • chan<- int — faqat yuborish uchun channel;
  • <-chan int — faqat qabul qilish uchun channel;
  • chan int — ikki tomonlama channel.

Ikki tomonlama channel funksiyaga uzatilganda mos yo‘nalishli turga avtomatik torayadi:

ch := make(chan int, 1)
send(ch)
fmt.Println(receive(ch))

Faqat qabul qiluvchi channelga yuborish yoki faqat yuboruvchi channeldan qabul qilish kompilyatsiya xatosi beradi. Bu API niyatini ko‘rsatadi va channelni noto‘g‘ri ishlatishning oldini oladi.

Faqat qabul qilish uchun berilgan channelni yopib bo‘lmaydi. Channelni yopish yuboruvchi tomonning vazifasi bo‘lgani uchun close yuborish imkoniga ega channel talab qiladi.

Qaysi channel turini tanlash kerak?

Holat Mos tanlov
Yuboruvchi va qabul qiluvchi aynan almashuv paytida uchrashishi kerak Bufersiz channel
Qisqa yuklama sakrashini cheklangan navbatda ushlash kerak Buferli channel
Funksiya faqat qiymat ishlab chiqaradi chan<- T parametr
Funksiya faqat qiymat iste’mol qiladi <-chan T parametr

Bufersiz channel “sekin”, buferli channel esa “tez” degan umumiy qoida yo‘q. To‘g‘ri tanlov talab qilinadigan sinxronizatsiya va navbat semantikasiga bog‘liq. Performance farqini aniq ish yukida benchmark bilan o‘lchash kerak.

Keng tarqalgan xatolar

Bufer deadlockni butunlay yo‘q qiladi deb o‘ylash

Bufer faqat sig‘imigacha sendni qabul qiluvchidan ajratadi. U to‘lgach send yana bloklanadi. Qabul qiluvchi hayot sikli va channel yopilishi baribir aniq bo‘lishi kerak.

Buferni cheksiz kattalashtirish

Katta bufer sekin iste’molchini tuzatmaydi. U muammoni kechiktiradi va xotira hamda latency sarfini oshiradi. Tizimga mos limit va backpressure belgilang.

FIFO global bajarilish tartibini beradi deb o‘ylash

FIFO bitta channelga muvaffaqiyatli yuborilgan qiymatlar tartibiga tegishli. Turli goroutinelarning scheduler tartibi kafolatlanmaydi.

len(ch) bilan sinxronizatsiya qilish

len natijasi darhol eskirishi mumkin. Qarorni channel amali yoki select orqali atomar tarzda ifodalang.

Funksiya parametrida ortiqcha huquq berish

Faqat yuboradigan funksiyaga chan T berish uning tasodifan qabul qilishiga ham imkon beradi. API da eng tor chan<- T yoki <-chan T turini ishlating.

Interviewda nimalarga e’tibor beriladi?

  • bufersiz send mos receive tayyor bo‘lguncha bloklanadi;
  • buferli send faqat buferda joy boricha qabul qiluvchisiz davom etadi;
  • channel FIFO, ammo turli goroutinelarning send tartibi kafolatlanmaydi;
  • bufer cheklangan navbat va backpressure vositasi;
  • len(ch) sinxronizatsiya kafolati bermaydi;
  • chan<- T faqat yuborish, <-chan T faqat qabul qilish uchun;
  • channel sig‘imi performance taxmini bilan emas, kerakli semantika va o‘lchov bilan tanlanadi.

Xulosa

Bufersiz channel yuboruvchi va qabul qiluvchini qiymat almashish vaqtida sinxronlashtiradi. Buferli channel ular orasiga cheklangan FIFO navbat qo‘yadi. Yo‘nalishli channel esa funksiya huquqlarini tur darajasida cheklaydi.

Bufer hajmini aniq limit sifatida tanlang. U sekin iste’molchini yoki noto‘g‘ri hayot siklini yashirish vositasi emas. Keyingi darsda bir nechta channel amali orasidan tayyorini tanlash uchun select operatorini o‘rganamiz.