مدیریت Context در گولنگ با context.Context

مدیریت درست context.Context یکی از کلیدهای ساخت برنامه‌های گولنگ قدرتمند و کم‌مصرف است. پکیج context این امکان را می‌دهد که مهلت اجرا، زمان‌ انتظار، لغو عملیات و حتی مقادیر مختلف را بین بخش‌های مختلف منتقل کنید. این کار به‌ویژه زمانی مهم است که بخواهید مطمئن شوید کارهای طولانی مثل کوئری‌های پایگاه داده، درخواست‌های API یا پردازش‌های پس‌زمینه بی‌دلیل منابع سیستم را برای همیشه اشغال نکنند.


مبانی context.Context

قبل از رفتن سراغ مثال‌های پیشرفته، یک مرور سریع از کاربرد های context داشته باشیم:

  • Cancelation: لغو کردن یک context باعث متوقف شدن تمام کارهای مرتبط با آن می‌شود.

  • Timeout: به‌طور خودکار بعد از مدت مشخصی context را لغو می‌کند.

  • Deadline: مشابه timeout است، اما زمان دقیق لغو را مشخص می‌کند.

  • Values: داده‌های مربوط به یک درخواست را از طریق context منتقل می‌کند.


مثال‌های پیشرفته

  1. 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
}
  1. 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")
    }
}
  1. 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)
}
  1. 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
}
  1. 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 ذخیره کنید.



0 🔥
0 🎉
0 😮
0 👍
0 💜
0 👏
میلاد خسروی
نویسنده کد نیوز

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

0+ نظر

برای ثبت نظر ابتدا ورود کنید.

0 نظر

    اولین نفر باش که نظر ثبت میکنی :) یعنی یه کامنت به ما نمیرسه 😁