برنامه نویس فان | Fun Developer یک آدم ساده که عاشق برنامه نویسی و کد زدنه :) تلاش میکنه تا به بقیه کمک کنه. توسعه دهنده هسته لاراول و فضای اوپن سورس. فاندر پرانتز و کد نیوز.

مدیریت درست context.Context یکی از کلیدهای ساخت برنامههای گولنگ قدرتمند و کممصرف است. پکیج context این امکان را میدهد که مهلت اجرا، زمان انتظار، لغو عملیات و حتی مقادیر مختلف را بین بخشهای مختلف منتقل کنید. این کار بهویژه زمانی مهم است که بخواهید مطمئن شوید کارهای طولانی مثل کوئریهای پایگاه داده، درخواستهای API یا پردازشهای پسزمینه بیدلیل منابع سیستم را برای همیشه اشغال نکنند.
مبانی context.Context
قبل از رفتن سراغ مثالهای پیشرفته، یک مرور سریع از کاربرد های context داشته باشیم:
-
Cancelation: لغو کردن یک context باعث متوقف شدن تمام کارهای مرتبط با آن میشود.
-
Timeout: بهطور خودکار بعد از مدت مشخصی context را لغو میکند.
-
Deadline: مشابه timeout است، اما زمان دقیق لغو را مشخص میکند.
-
Values: دادههای مربوط به یک درخواست را از طریق context منتقل میکند.
مثالهای پیشرفته
- Cascading Cancellations:
Context ها میتوانند تو در تو باشند، بهطوری که یک context والد بتواند فرزندان خود را لغو کند. این ویژگی برای هماهنگی بین وظایف مختلف بسیار حیاتی است:
package main
import (
"context"
"fmt"
"time"
)
func performTask(ctx context.Context, taskID string) {
select {
case <-time.After(2 * time.Second): // Simulate task duration
fmt.Println("Task completed:", taskID)
case <-ctx.Done(): // React to cancellation
fmt.Println("Task canceled:", taskID, "Reason:", ctx.Err())
}
}
func main() {
parentCtx, cancel := context.WithCancel(context.Background())
// Launch child tasks
for i := 1; i <= 3; i++ {
go performTask(parentCtx, fmt.Sprintf("Task-%d", i))
}
time.Sleep(1 * time.Second) // Let tasks start
cancel() // Cancel all tasks
time.Sleep(3 * time.Second) // Wait to observe cancellation
}
-
Context Timeouts for Long-Running Operations:
استفاده از timeout بسیار مهم است تا از اجرای بیپایان وظایف جلوگیری شود.
package main
import (
"context"
"fmt"
"time"
)
func fetchData(ctx context.Context) error {
select {
case <-time.After(3 * time.Second): // Simulated delay
return fmt.Errorf("data fetch timeout")
case <-ctx.Done():
return ctx.Err() // Return context error
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
err := fetchData(ctx)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Data fetched successfully")
}
}
-
Passing Context Between Layers:
معمولاً context ها در طول Call Stack منتقل میشوند تا بتوان مقادیر را به اشتراک گذاشت و اجرای برنامه را کنترل کرد.
package main
import (
"context"
"fmt"
"net/http"
)
// Simulate database query
func queryDatabase(ctx context.Context) error {
if userID := ctx.Value("userID"); userID != nil {
fmt.Println("Querying database for user:", userID)
select {
case <-time.After(2 * time.Second): // Simulate query time
return nil
case <-ctx.Done():
return ctx.Err()
}
}
return fmt.Errorf("userID not found in context")
}
// HTTP handler
func handler(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "userID", 12345)
if err := queryDatabase(ctx); err != nil {
http.Error(w, "Request failed: "+err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintln(w, "Query succeeded")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
-
Graceful Shutdowns:
از context.Context برای مدیریت پاکسازی و آزادسازی منابع هنگام خاموش شدن برنامه استفاده کنید.
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
// Capture OS signals
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
// Simulate a worker
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Shutting down worker")
return
default:
fmt.Println("Worker is running")
time.Sleep(1 * time.Second)
}
}
}(ctx)
// Wait for OS signal
<-signalChan
fmt.Println("Signal received, shutting down")
cancel() // Cancel context
time.Sleep(2 * time.Second) // Allow cleanup
}
-
Context with Goroutines:
لغو یک context در میان چندین گوروتین (Goroutine) باعث میشود همه آنها بهصورت منظم و بدون مشکل خاتمه یابند.
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
func worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("Worker %d stopping: %v
", id, ctx.Err())
return
default:
fmt.Printf("Worker %d processing...
", id)
time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
for i := 1; i <= 5; i++ {
go worker(ctx, i)
}
time.Sleep(3 * time.Second) // Wait for workers to finish
fmt.Println("Main function exiting")
}
بهترین شیوهها برای مدیریت Context ها:
-
انتقال Context: همیشه
context.Context
را بهصورت صریح و بهعنوان پارامتر اول به توابعی که به آن نیاز دارند منتقل کنید. -
عدم ذخیرهسازی Context: هیچگاه یک context را در یک struct ذخیره نکنید؛ بهجای آن آن را در طول Call Stack منتقل کنید.
-
رعایت مهلتها: در فرآیندهای طولانی، مرتباً ctx.Done() را بررسی کنید.
-
استفاده هوشمندانه از مقادیر: تنها دادههای سبک و مربوط به یک درخواست را در context ذخیره کنید.
اولین نفر باش که نظر ثبت میکنی :) یعنی یه کامنت به ما نمیرسه 😁