Context package
Та интернэтээс өгөгдөл татаж байна гэж бодъё. Хэрэглэгч хүлээхдээ залхаад хуудасыг хааж орхив. Танай программ мэдэхгүй хэвээр ажиллаж, нөөц зарцуулж байна — энэ нь муугийн тэмдэг. context package нь яг энэ асуудлыг шийддэг: хэрэглэгч цуцалбал бүх downstream хүсэлтүүд мөн цуцлагдана, хугацаа дуусвал автоматаар зогсдог. Go-ийн backend хөгжүүлэлтэд context нь заавал ойлгох ёстой хэрэгсэл юм.
Context гэж юу вэ?
context.Context бол интерфэйс бөгөөд гурван зүйлийг агуулдаг:
- Цуцлах дохио — хэрэглэгч эсвэл программ хүсэлтийг дундаас зогсоох боломж
- Deadline / Timeout — хугацааны хязгаарлалт
- Утга — request-ийн туршид нэмэлт мэдээлэл дамжуулах
package main
import (
"context"
"fmt"
"time"
)
func удаанАжиллагч(ctx context.Context, нэр string) error {
// Context-ийг байнга шалгах хэрэгтэй
select {
case <-time.After(3 * time.Second): // 3 секунд ажиллана
fmt.Printf("%s: дууслаа\n", нэр)
return nil
case <-ctx.Done(): // Context цуцлагдсан эсэх
fmt.Printf("%s: цуцлагдлаа — %v\n", нэр, ctx.Err())
return ctx.Err()
}
}
func main() {
// 1 секундын timeout-тэй context үүсгэх
ctx, цуцлах := context.WithTimeout(context.Background(), 1*time.Second)
defer цуцлах() // Заавал дуудах!
err := удаанАжиллагч(ctx, "Ажилт 1")
if err != nil {
fmt.Println("Алдаа:", err) // context deadline exceeded
}
}
ctx.Done() channel нь context цуцлагдах үед хаагддаг. select ашиглан энэ дохиог хүлээн авч ажлыг зогсоодог.
WithTimeout ба WithDeadline
Хамгийн өргөн хэрэглэгддэг context функцүүд:
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func вэбсайтТатах(url string) error {
// 5 секундын дотор дуусахгүй бол цуцлах
ctx, цуцлах := context.WithTimeout(context.Background(), 5*time.Second)
defer цуцлах()
// Context-тэй HTTP хүсэлт үүсгэх
хүсэлт, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return err
}
клиент := &http.Client{}
хариу, err := клиент.Do(хүсэлт)
if err != nil {
return fmt.Errorf("хүсэлт амжилтгүй: %w", err)
}
defer хариу.Body.Close()
fmt.Printf("Статус: %d\n", хариу.StatusCode)
return nil
}
func main() {
err := вэбсайтТатах("https://example.com")
if err != nil {
fmt.Println("Алдаа:", err)
return
}
fmt.Println("Амжилттай татлаа!")
}
// WithDeadline: тодорхой цаг тогтоох
func тогтооснЦагтай() {
дуусах := time.Now().Add(2 * time.Second) // 2 секундын дараа
ctx, цуцлах := context.WithDeadline(context.Background(), дуусах)
defer цуцлах()
select {
case <-time.After(3 * time.Second):
fmt.Println("Дууслаа")
case <-ctx.Done():
fmt.Println("Deadline дууслаа:", ctx.Err())
}
}
WithTimeout нь "одоогоос X хугацааны дараа", WithDeadline нь "энэ тодорхой цагт" гэсэн ялгаатай.
WithCancel — гараар цуцлах
Хэрэглэгч "болих" товч дарахад бүх ажлыг цуцлах хэрэгтэй байдаг. WithCancel нь гараар цуцлах боломж олгодог:
package main
import (
"context"
"fmt"
"sync"
"time"
)
func ажилт(ctx context.Context, дугаар int, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Printf("Ажилт %d: зогслоо\n", дугаар)
return
default:
fmt.Printf("Ажилт %d: ажиллаж байна...\n", дугаар)
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, цуцлах := context.WithCancel(context.Background())
var wg sync.WaitGroup
// 3 goroutine эхлүүлэх
for i := 1; i <= 3; i++ {
wg.Add(1)
go ажилт(ctx, i, &wg)
}
// 2 секундын дараа бүгдийг цуцлах
time.Sleep(2 * time.Second)
fmt.Println("Бүгдийг цуцалж байна...")
цуцлах() // Энэ дуудалт бүх goroutine-д ctx.Done()-ийг хаадаг
wg.Wait()
fmt.Println("Бүгд зогслоо")
}
цуцлах() нэг удаа дуудахад тэр context-тэй холбогдсон бүх goroutine дохиог авна — энэ нь маш хүчтэй.
Context-ээр утга дамжуулах
HTTP middleware-ийн хичээлд харсан context.WithValue нь request-ийн туршид мэдээлэл дамжуулдаг:
package main
import (
"context"
"fmt"
"net/http"
)
type contextТүлхүүр string
const (
хэрэглэгчIDТүлхүүр contextТүлхүүр = "хэрэглэгчID"
хүсэлтIDТүлхүүр contextТүлхүүр = "хүсэлтID"
)
// Middleware: context-т утга хийнэ
func authMiddleware(дараачийн http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Жишээд зориулж хатуу кодолсон — бодит тохиолдолд token шалгана
ctx := context.WithValue(r.Context(), хэрэглэгчIDТүлхүүр, "user-42")
ctx = context.WithValue(ctx, хүсэлтIDТүлхүүр, "req-abc123")
дараачийн.ServeHTTP(w, r.WithContext(ctx))
})
}
// Handler: context-оос утга уншина
func профайлHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
хэрэглэгчID, _ := ctx.Value(хэрэглэгчIDТүлхүүр).(string)
хүсэлтID, _ := ctx.Value(хүсэлтIDТүлхүүр).(string)
fmt.Fprintf(w, "Хэрэглэгч: %s\nХүсэлт: %s\n", хэрэглэгчID, хүсэлтID)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /profile", профайлHandler)
http.ListenAndServe(":8080", authMiddleware(mux))
}
Context-т утга хийхдээ string бус өөрийн тусгай төрөл (contextТүлхүүр) ашигла. Энэ нь өөр package-ийн утгуудтай мөргөлдөхөөс сэргийлдэг.
Context дамжуулах дүрэм
Context ашиглахад хэд хэдэн чухал дүрэм байдаг:
// ✅ Зөв: context-ийг эхний аргумент болгон дамжуул
func өгөгдөлАвах(ctx context.Context, id int) (string, error) {
// ctx ашиглан DB хүсэлт хий
return "", nil
}
// ✅ Зөв: context-ийг struct-т хадгалах бус, функцэд дамжуул
type Үйлчилгээ struct {
db *sql.DB
// context.Context-ийг энд бүү хадгал
}
func (у *Үйлчилгээ) ХэрэглэгчАвах(ctx context.Context, id int) (*Хэрэглэгч, error) {
row := у.db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", id)
// ...
return nil, nil
}
// ❌ Буруу: context-ийг struct-т хадгалах
type БуруугийнБүтэц struct {
ctx context.Context // Энэ буруу хэлбэр
}
context.Background() нь хамгийн дээд түвшний context — main функц болон тестэд ашигладаг. context.TODO() нь "энд context ирэх ёстой, дараа засна" гэх үед ашигладаг.
Дараагийн хичээлд:
Context package-ийг судаллаа. Дараагийн хичээлд бидний бичсэн Go программыг хэрхэн build хийж, production сервер дээр deploy хийх вэ гэдгийг харна. Эцсийн хичээлийн өмнөх бэлтгэл болох энэ хичээлд Dockerfile, binary build, орчны хувьсагч зэргийг судална.