41 / 68

Kanal yo'nalishlari va yopish

Kanalni faqat yuborish yoki faqat qabul qilish uchun cheklash

Oddiy kanal (chan int) orqali ham yuborish, ham qabul qilish mumkin. Lekin funksiya faqat bittasini bajarishi kerak bo'lsa, bu niyatni kanal yo'nalishi (channel direction) orqali kompilyator darajasida aniq belgilash mumkin — bu xatolarni erta, kompilyatsiya bosqichidayoq ushlab qolishga yordam beradi.

example.go
func send(ch chan<- int, val int) { // faqat YUBORISH uchun kanal
    ch <- val
}

func receive(ch <-chan int) int { // faqat QABUL QILISH uchun kanal
    return <-ch
}
  • chan<- int — bu kanaldan faqat yuborish mumkin.
  • <-chan int — bu kanaldan faqat qabul qilish mumkin.

1. Amalda ko'ramiz

example.go
package main

import "fmt"

func send(ch chan<- int, val int) {
    ch <- val
}

func receive(ch <-chan int) int {
    return <-ch
}

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

    send(ch, 42)

    fmt.Println(receive(ch))
}

Natija:

kod
42

Oddiy ikki yo'nalishli kanal (chan int) avtomatik ravishda kerakli bir yo'nalishli turga aylanadi — send() chaqirilganda Go kompilyatori uni chan<- int sifatida qabul qiladi. Lekin teskarisi ishlamaydi: agar receive() ichida ch <- 5 kabi yuborishga urinsangiz, kompilyator xatolik beradi — bu esa dasturchining niyatini aniq va xavfsiz qiladi.

[!NOTE] Bu pattern, ayniqsa, katta dasturlarda "kim bu kanalga yozadi, kim o'qiydi" degan javobgarlikni funksiya imzosining o'zidan bilib olish imkonini beradi — kodni o'qigan boshqa dasturchi hujjatga qaramasdan tushunadi.


2. Kanalni yopish: close()

Agar yuboruvchi tomon "boshqa hech qanday ma'lumot yubormayman" deb bildirmoqchi bo'lsa, close() funksiyasi ishlatiladi:

example.go
package main

import "fmt"

func producer(ch chan<- int) {
    for i := 1; i <= 5; i++ {
        ch <- i
    }
    close(ch) // barcha ma'lumot yuborilgach, kanalni yopamiz
}

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

    go producer(ch)

    // range kanal yopilishi bilan avtomatik to'xtaydi
    for v := range ch {
        fmt.Println(v)
    }

    fmt.Println("Kanal yopilgach sikl to'xtadi")
}

Natija:

kod
1
2
3
4
5
Kanal yopilgach sikl to'xtadi

for v := range ch — kanal yopilmaguncha undan qiymatlarni birin-ketin qabul qilib turadi, kanal yopilgan zahoti sikl o'z-o'zidan to'xtaydi (break yozishning hojati yo'q).


3. Kanal yopilganini tekshirish: v, ok := <-ch

mapdagi "comma-ok" idiomasiga o'xshab, kanaldan qiymat olishda ham kanal yopilgan-yopilmaganini bilish mumkin:

example.go
package main

import "fmt"

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

    ch <- 10
    close(ch)

    v1, ok1 := <-ch // hali yopilishdan oldin yuborilgan qiymat bor
    fmt.Println(v1, ok1)

    v2, ok2 := <-ch // kanal bo'sh VA yopilgan
    fmt.Println(v2, ok2)
}

Natija:

kod
10 true
0 false

[!IMPORTANT] Yopilgan kanaldan qiymat olish xato emas — agar buferda hali yuborilgan qiymatlar qolgan bo'lsa, ular baribir olinaveradi (ok = true), faqat bufer butunlay bo'shagandan keyingina ok = false va nol qiymat (zero value) qaytadi. Bu — Goning kanallar orqali "ish tugadi" signalini xavfsiz uzatishga imkon beruvchi asosiy mexanizmi.


4. Ikkita muhim qoida (xato qilinsa panic!)

example.go
ch := make(chan int, 1)
close(ch)
ch <- 1 // PANIC: send on closed channel
example.go
ch := make(chan int)
close(ch)
close(ch) // PANIC: close of closed channel

[!WARNING]

  • Yopilgan kanalga yuborish darhol panic beradi.
  • Bitta kanalni ikki marta yopish ham panic beradi.
  • Kanalni har doim faqat yuboruvchi tomon yopishi kerak, hech qachon qabul qiluvchi tomon emas — chunki qabul qiluvchi kanalga yana qancha ma'lumot kelishini bilmaydi.

Xulosa: qachon qaysi funksiya kerak?

VazifaKod
Faqat yuborish uchun kanal parametrifunc f(ch chan<- Tur)
Faqat qabul qilish uchun kanal parametrifunc f(ch <-chan Tur)
Kanalni yopish ("boshqa ma'lumot yo'q" signali)close(ch)
Kanal yopilgan-yopilmaganini tekshirishv, ok := <-ch
Kanal yopilguncha barcha qiymatlarni olishfor v := range ch { ... }