64 / 73

Context

Oldingi boblarda biz alohida cancel kanallaridan foydalandik. Ammo haqiqiy loyihalarda bitta foydalanuvchi so'rovi (masalan, HTTP so'rov) o'nlab mikroservislarni, ma'lumotlar bazasini va fon goroutine'larini ishga solishi mumkin. Agar foydalanuvchi brauzer oynasini yopib qo'ysa, unga bog'liq barcha o'nlab jarayonlarni bir lahzada kaskadli to'xtatish kerak bo'ladi. Go standart kutubxonasidagi context paketi aynan shu vazifa uchun yaratilgan!

Bob mundarijasi:


Kanal orqali bekor qilishdan Context ga o'tish

Oldingi bobdagi bekor qilish modelimizni eslaylik:

example.go
cancel := make(chan struct{})
go worker(cancel)
close(cancel) // to'xtatish

Bu juda yaxshi ishladi, ammo:

  1. Nega bekor bo'lganini bilib bo'lmaydi (vaqt tugadimi? Foydalanuvchi bekor qildimi? Server to'xtadimi?).
  2. Kaskadli bog'liqliklarni (daraxtsimon tuzilmani) qo'lda boshqarish juda qiyin.
  3. Funksiyalararo so'rov identifikatorlarini (Request ID, Trace ID) uzatish uchun yana alohida parametrlar kerak.

Shuning uchun Go yaratuvchilari barcha concurrency operatsiyalari uchun standart interfeys — context.Contextni yaratdilar.


context.WithCancel

Eng sodda kontekst — bu qo'lda bekor qilinadigan kontekst:

example.go
package main

import (
	"context"
	"fmt"
	"time"
)

func worker(ctx context.Context, id int) {
	for {
		select {
		case <-ctx.Done(): // kanal yopilganda signal oladi
			fmt.Printf("Worker #%d to'xtatildi, sabab: %v
", id, ctx.Err())
			return
		default:
			fmt.Printf("Worker #%d ishlamoqda...
", id)
			time.Sleep(50 * time.Millisecond)
		}
	}
}

func main() {
	// context.Background() — bo'sh ildiz kontekst
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // resurslarni tozalash uchun har doim defer cancel() qo'yiladi

	go worker(ctx, 1)

	time.Sleep(120 * time.Millisecond)
	fmt.Println("Main: bekor qilish chaqirilmoqda...")
	cancel() // goroutine'larga bekor qilish signali yuboriladi!

	time.Sleep(50 * time.Millisecond)
}

Natija:

text
Worker #1 ishlamoqda...
Worker #1 ishlamoqda...
Worker #1 ishlamoqda...
Main: bekor qilish chaqirilmoqda...
Worker #1 to'xtatildi, sabab: context canceled

ctx.Done() — bu tayyor <-chan struct{} qaytaruvchi kanaldir. cancel() chaqirilishi bilan ushbu kanal avtomatik ravishda yopiladi va ctx.Err() metodi context.Canceled xatosini qaytaradi.


context.WithTimeout

Agar operatsiyaning maksimal bajarilish vaqtini cheklash kerak bo'lsa, context.WithTimeout ishlatiladi:

example.go
package main

import (
	"context"
	"fmt"
	"time"
)

func executeQuery(ctx context.Context) error {
	select {
	case <-time.After(300 * time.Millisecond): // uzoq so'rov
		fmt.Println("So'rov muvaffaqiyatli yakunlandi")
		return nil
	case <-ctx.Done():
		return ctx.Err() // agar vaqt o'tsa context.DeadlineExceeded qaytadi
	}
}

func main() {
	// 100 millisekundlik qat'iy limit:
	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()

	err := executeQuery(ctx)
	if err != nil {
		fmt.Println("Xatolik:", err)
	}
}

Natija:

text
Xatolik: context deadline exceeded

Topshiriq 1: WithTimeout yordamida API so'rovini cheklash

Berilgan vaqt ichida ma'lumotlar bazasidan javob olishni simulyatsiya qiluvchi funksiyaga context.WithTimeout orqali limit o'rnating.

example.go
package main

import (
	"context"
	"fmt"
	"time"
)

func queryDatabase(ctx context.Context) (string, error) {
	select {
	case <-time.After(80 * time.Millisecond):
		return "Foydalanuvchi ma'lumotlari", nil
	case <-ctx.Done():
		return "", ctx.Err()
	}
}

func main() {
	// TODO: 50 millisekundlik timeout bilan context yarating va queryDatabase ni chaqiring
}

Ota va bola kontekstlar

Kontekstlar ierarxik daraxt (tree) shaklida bo'ladi:

kod
                [ Background ]
                      │
              [ WithCancel (Ota) ]
              ┌───────┴───────┐
      [ WithTimeout (Bola 1) ] [ WithValue (Bola 2) ]

Eng muhim oltin qoida: Ota kontekst bekor qilinsa, uning barcha bola kontekstlari ham avtomatik va bir lahzada bekor qilinadi! Ammo bolaning bekor qilinishi otaga yoki boshqa aka-uka kontekstlarga aslo ta'sir qilmaydi.


context.WithValue

Kontekst orqali butun chaqiruvlar zanjiri (middleware, servislar, repositoriylar) bo'ylab so'rovga oid metama'lumotlarni (masalan, TraceID, UserID) uzatish mumkin:

example.go
type keyType string

const traceKey keyType = "trace_id"

ctx := context.WithValue(context.Background(), traceKey, "trace-abc-123")

// Boshqa ichki funksiya ichida:
if traceID, ok := ctx.Value(traceKey).(string); ok {
	fmt.Println("Joriy trace:", traceID)
}

[!WARNING] Kontekstga ixtiyoriy funksiya parametrlarini yoki biznes ma'lumotlarni solmang! Unda faqat so'rov kontekstiga oid bo'lgan metama'lumotlar (autentifikatsiya tokeni, xavfsizlik konteksti, monitoring ID) saqlanishi kerak.


Eng yaxshi amaliyotlar

  1. Har doim birinchi parametr: Kontekst funksiya parametrlarining eng birinchisi bo'lishi kerak: func DoSomething(ctx context.Context, arg string) error.
  2. Struct ichida saqlamang: Kontekstni struct maydoni qilib saqlamang, uni funksiyadan funksiyaga ochiq parametr sifatida uzating.
  3. nil uzatmang: Hech qachon nil context uzatmang. Agar kontekst yo'q bo'lsa, context.TODO() dan foydalaning.
  4. Har doim defer cancel(): WithCancel, WithTimeout yoki WithDeadline ishlatganingizda, resurslar sizib chiqmasligi uchun har doim defer cancel() chaqiring.

Xulosa va keyingi qadamlar

Ushbu bobda biz Go concurrency'ning yuragi bo'lgan context paketini to'liq ko'rib chiqdik.

Shu bilan 01. Concurrency basics bo'limi yakunlandi! Keyingi bo'lim: 02. Synchronization — bir vaqtda ishlaydigan goroutine'lar o'rtasida ma'lumotlarni xavfsiz sinxronlash (Wait groups, Data races, Race conditions, Semaphores, Signaling, Atomics)!