63 / 73

Time

Concurrent dasturlarda vaqt o'lchami juda muhim: tarmoq so'rovlari ma'lum vaqt ichida javob berishi (timeout), API so'rovlari sekundiga belgilangan me'yordan oshmasligi (rate limiting / throttling) va orqa fonda davriy vazifalar aniq intervallarda bajarilib turishi kerak.

Bob mundarijasi:


Tezlikni cheklash

Tasavvur qiling, siz tashqi API servisiga (masalan, to'lov tizimiga yoki SMS shlyuziga) so'rovlar yuborayapsiz. Ushbu servis qat'iy qoida qo'ygan: "Sekundiga ko'pi bilan 5 ta so'rov!". Agar siz bir vaqtda 50 ta goroutine ochib so'rov yuborsangiz, server sizni 429 Too Many Requests xatosi bilan bloklab qo'yadi.

Buni hal qilish uchun throttling (tezlikni cheklash) usulidan foydalaniladi:

example.go
package main

import (
	"fmt"
	"time"
)

func main() {
	requests := make(chan int, 5)
	for i := 1; i <= 5; i++ {
		requests <- i
	}
	close(requests)

	// Har 200 millisekundda bitta signal beruvchi cheklovchi:
	limiter := time.Tick(200 * time.Millisecond)

	for req := range requests {
		<-limiter // har bir so'rovdan oldin cheklovchi signalini kutamiz
		fmt.Printf("So'rov #%d bajarildi: %s
", req, time.Now().Format("15:04:05.000"))
	}
}

Bu yerda har bir so'rov roppa-rosa 200ms oraliq bilan (sekundiga 5 ta) yuboriladi.


Qarshi bosim

Agar ma'lumotlar ishlab chiqaruvchi (producer) iste'molchidan (consumer) tezroq ishlayotgan bo'lsa, xotira to'lib ketmasligi uchun ishlab chiqaruvchini sekinlashtirish kerak. Bu backpressure (qarshi bosim) deb ataladi.

Go tilida buferlangan kanallar va sinxron kutish mexanizmlari tabiiy ravishda backpressure vazifasini o'taydi: bufer to'lgach, yozuvchi avtomatik to'xtab, iste'molchining bo'shashini kutadi.


Operatsiya vaqti tugashi

Tarmoq orqali ulanishda yoki uzoq hisob-kitoblarda operatsiya kutilganidan ko'ra uzoqroq cho'zilib ketsa, dasturni abadiy osilib qolishdan saqlash uchun timeout o'rnatiladi.

Go tilida bu select va time.After() yordamida ajoyib tarzda hal etiladi:

example.go
package main

import (
	"fmt"
	"time"
)

func fetchData() <-chan string {
	ch := make(chan string, 1)
	go func() {
		time.Sleep(200 * time.Millisecond) // uzoq davom etuvchi operatsiya
		ch <- "Ma'lumotlar tayyor"
	}()
	return ch
}

func main() {
	resCh := fetchData()

	select {
	case res := <-resCh:
		fmt.Println("Muvaffaqiyat:", res)
	case <-time.After(100 * time.Millisecond): // 100ms dan oshsa timeout!
		fmt.Println("Xatolik: Operatsiya vaqti tugadi (Timeout)!")
	}
}

Natija:

text
Xatolik: Operatsiya vaqti tugadi (Timeout)!

time.After(duration) belgilangan vaqtdan so'ng o'z kanaliga bitta xabar yuboradi. Agar resCh undan oldin kelsa — ma'lumot qabul qilinadi; agar vaqt o'tib ketsa — timeout bloki ishga tushadi.


Topshiriq 1: So'rov uchun timeout o'rnatish

Foydalanuvchi ma'lumotlarini yuklovchi fetchUser(timeout time.Duration) funksiyasini yozing. Agar ma'lumot berilgan vaqt ichida kelsa, uni qaytarsin; aks holda timeout xatosi bersin.

example.go
package main

import (
	"errors"
	"fmt"
	"time"
)

func getUser(id int) <-chan string {
	ch := make(chan string, 1)
	go func() {
		time.Sleep(150 * time.Millisecond)
		ch <- "User: Alisher"
	}()
	return ch
}

func fetchUserWithTimeout(id int, limit time.Duration) (string, error) {
	// TODO: getUser natijasini va time.After(limit) ni select orqali tekshiring
	return "", nil
}

func main() {
	res, err := fetchUserWithTimeout(1, 100*time.Millisecond)
	if err != nil {
		fmt.Println("Xato:", err)
	} else {
		fmt.Println("Natija:", res)
	}
}

Taymer (Timer: time.NewTimer)

time.After() sodda holatlar uchun juda qulay, biroq uning kamchiligi bor: u tugamaguncha Garbage Collector tomonidan tozalab tashlanmaydi. Agar siz sikl ichida yuzlab time.After() chaqirsangiz, ortiqcha xotira sarflanadi.

Agar taymerni muddatidan oldin to'xtatish (cancel qilish) yoki qayta ishga tushirish (reset) zarur bo'lsa, time.NewTimer ishlatiladi:

example.go
timer := time.NewTimer(500 * time.Millisecond)

go func() {
	<-timer.C
	fmt.Println("Taymer ishladi!")
}()

// Agar hodisa ro'y berib, taymer kerak bo'lmay qolsa:
stop := timer.Stop()
if stop {
	fmt.Println("Taymer o'z vaqtida to'xtatildi!")
}

Ticker (time.NewTicker)

Agar vazifani bir martagina emas, balki har $X$ soniyada davriy (periodik) bajarib turish kerak bo'lsa (masalan, keshni tozalash, server salomatligini tekshirish — heartbeat), time.NewTicker ishlatiladi:

example.go
package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(100 * time.Millisecond)
	defer ticker.Stop() // Ticker bilan ishlaganda albatta Stop() chaqirish shart!

	done := make(chan struct{})
	go func() {
		time.Sleep(350 * time.Millisecond)
		close(done)
	}()

	for {
		select {
		case <-done:
			fmt.Println("Ticker to'xtatildi.")
			return
		case t := <-ticker.C:
			fmt.Println("Tick:", t.Format("15:04:05.000"))
		}
	}
}

Topshiriq 2: Heartbeat andozasi

Tizimning tirikligini bildirish uchun har 50 millisekundda kanalga struct{}{} signali yuboruvchi heartbeat funksiyasini yozing. Tashqaridan done kanali yopilganda ticker darhol to'xtatilsin.

example.go
package main

import (
	"fmt"
	"time"
)

func heartbeat(done <-chan struct{}, interval time.Duration) <-chan struct{} {
	pulse := make(chan struct{})
	// TODO: NewTicker yordamida har interval vaqtda pulse ga signal yuboring
	return pulse
}

func main() {
	done := make(chan struct{})
	pulses := heartbeat(done, 50*time.Millisecond)

	go func() {
		time.Sleep(160 * time.Millisecond)
		close(done)
	}()

	for range pulses {
		fmt.Println("Yurak urishi: tirik")
	}
	fmt.Println("Tizim to'xtatildi")
}
example.go
package main

import (
	"fmt"
	"time"
)

func heartbeat(done <-chan struct{}, interval time.Duration) <-chan struct{} {
	pulse := make(chan struct{})
	go func() {
		defer close(pulse)
		ticker := time.NewTicker(interval)
		defer ticker.Stop()

		for {
			select {
			case <-done:
				return
			case <-ticker.C:
				select {
				case pulse <- struct{}{}:
				default:
				}
			}
		}
	}()
	return pulse
}

func main() {
	done := make(chan struct{})
	pulses := heartbeat(done, 50*time.Millisecond)

	go func() {
		time.Sleep(160 * time.Millisecond)
		close(done)
	}()

	for range pulses {
		fmt.Println("Yurak urishi: tirik")
	}
	fmt.Println("Tizim to'xtatildi")
}

Xulosa va keyingi qadamlar

Ushbu bobda vaqt bilan ishlashning asosiy vositalarini o'rgandik:

  • Throttling va Rate limiting orqali tizim yuklamasini me'yorda ushlash;
  • time.After yordamida operatsiyalarga timeout belgilash;
  • time.NewTimer va time.NewTicker orqali resurslarni oqilona boshqarish va to'xtatish;
  • Heartbeat andozasi orqali servislar salomatligini kuzatish.

Keyingi bob: 05. Context (Kontekst va kaskadli bekor qilish)!