43 / 68

select bilan murakkab holatlar

Oldingi darsning davomi

select darsida ikkita kanal orasidan tanlash va default holatini ko'rgan edik. Bu darsda selectning amaliyotda eng ko'p ishlatiladigan ikki qo'llanilishini ko'ramiz: timeout (vaqt chegarasi) va bloklanmaydigan (non-blocking) yuborish/qabul qilish.


1. Vaqt chegarasi (timeout): time.After()

Ba'zan biror amal (masalan, tarmoq so'rovi) juda uzoq davom etib ketishi mumkin, va biz uni abadiy kutishni xohlamaymiz. Buning uchun selectni time.After() bilan birga ishlatamiz:

example.go
package main

import (
    "fmt"
    "time"
)

func slowOperation(ch chan<- string) {
    time.Sleep(2 * time.Second) // 2 soniya davom etadigan "sekin" amal
    ch <- "natija tayyor"
}

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

    go slowOperation(ch)

    select {
    case result := <-ch:
        fmt.Println(result)

    case <-time.After(500 * time.Millisecond):
        // agar 500ms ichida ch dan hech narsa kelmasa, shu holat ishga tushadi
        fmt.Println("Vaqt tugadi! Amal juda uzoq davom etyapti")
    }
}

Natija:

kod
Vaqt tugadi! Amal juda uzoq davom etyapti

slowOperation() 2 soniyadan keyin javob bergan bo'lardi, lekin time.After(500 * time.Millisecond) atigi 500 millisekunddan keyin "signal" beradi, va select qaysi holat birinchi tayyor bo'lsa, o'shani tanlaydi. Natijada dastur 2 soniya kutib o'tirmasdan, tezroq "vaqt tugadi" deb javob qaytaradi.

[!IMPORTANT] time.After(d) har chaqirilganda yangi kanal va yangi timer yaratadi. Agar select sikl ichida ko'p marta ishlatilsa, bu ortiqcha xotira sarflashi mumkin. Bunday hollarda bitta time.NewTimer() yaratib, uni qayta-qayta Reset() qilish tavsiya etiladi — lekin oddiy, bir martalik timeout holatlari uchun time.After() to'liq yetarli va eng ko'p ishlatiladigan usul.

Bu pattern, ayniqsa, context.WithTimeout() bilan birga (Standard Library bo'limidagi context darsida ko'rgan edik) tashqi API so'rovlariga vaqt chegarasi qo'yishda juda keng qo'llaniladi.


2. Bloklanmaydigan qabul qilish: select + default

Oddiy <-ch agar kanalda hech narsa bo'lmasa, kutadi. Ba'zan esa "agar hozir tayyor ma'lumot bo'lsa olib qol, bo'lmasa darhol davom et" degan xatti-harakat kerak bo'ladi:

example.go
package main

import "fmt"

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

    select {
    case v := <-ch:
        fmt.Println("Qabul qilindi:", v)
    default:
        // ch da hech narsa yo'q, lekin dastur kutib QOLMAYDI
        fmt.Println("Hech narsa yo'q, davom etamiz")
    }
}

Natija:

kod
Hech narsa yo'q, davom etamiz

3. Bloklanmaydigan yuborish

Xuddi shu pattern yuborish uchun ham ishlaydi — agar hech kim qabul qilishga tayyor bo'lmasa, yuborish shunchaki "o'tkazib yuboriladi":

example.go
package main

import "fmt"

func main() {
    ch := make(chan int) // hech kim o'qimayapti

    select {
    case ch <- 1:
        fmt.Println("Yuborildi")
    default:
        fmt.Println("Hech kim qabul qilmayapti, yuborilmadi")
    }
}

Natija:

kod
Hech kim qabul qilmayapti, yuborilmadi

Bu, masalan, log yozuvlarini "agar imkoni bo'lsa" jo'natish, aks holda uni shunchaki tashlab yuborish (log kanali to'lib ketganda dasturni sekinlashtirib qo'ymaslik) kabi hollarda foydalaniladi.


Xulosa: qachon qaysi pattern kerak?

VazifaPattern
Amalga vaqt chegarasi qo'yishselect { case <-ch: ...; case <-time.After(d): ... }
Kanalda ma'lumot bor-yo'qligini kutmasdan tekshirishselect { case v := <-ch: ...; default: ... }
Band bo'lsa yuborishni o'tkazib yuborishselect { case ch <- v: ...; default: ... }

[!NOTE] select ichidagi default holati mavjud bo'lsa, select hech qachon kutmaydi — agar boshqa hech qaysi case darhol tayyor bo'lmasa, default zudlik bilan ishga tushadi. default yozilmasa esa, select kamida bitta case tayyor bo'lguncha (yoki time.After kabi vaqt asosidagi holat ishga tushguncha) kutadi.