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!
Oldingi bobdagi bekor qilish modelimizni eslaylik:
cancel := make(chan struct{})
go worker(cancel)
close(cancel) // to'xtatish
Bu juda yaxshi ishladi, ammo:
Shuning uchun Go yaratuvchilari barcha concurrency operatsiyalari uchun standart interfeys — context.Contextni yaratdilar.
Eng sodda kontekst — bu qo'lda bekor qilinadigan kontekst:
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:
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.
Agar operatsiyaning maksimal bajarilish vaqtini cheklash kerak bo'lsa, context.WithTimeout ishlatiladi:
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:
Xatolik: context deadline exceeded
Berilgan vaqt ichida ma'lumotlar bazasidan javob olishni simulyatsiya qiluvchi funksiyaga context.WithTimeout orqali limit o'rnating.
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
}
Kontekstlar ierarxik daraxt (tree) shaklida bo'ladi:
[ 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.
Kontekst orqali butun chaqiruvlar zanjiri (middleware, servislar, repositoriylar) bo'ylab so'rovga oid metama'lumotlarni (masalan, TraceID, UserID) uzatish mumkin:
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.
func DoSomething(ctx context.Context, arg string) error.nil uzatmang: Hech qachon nil context uzatmang. Agar kontekst yo'q bo'lsa, context.TODO() dan foydalaning.defer cancel(): WithCancel, WithTimeout yoki WithDeadline ishlatganingizda, resurslar sizib chiqmasligi uchun har doim defer cancel() chaqiring.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)!