What is Context?

The context package in Go provides a way to carry deadlines, cancellation signals, and other request-scoped data across API boundaries. It is particularly useful in concurrent programming, where managing the lifecycle of goroutines is essential for resource management and preventing memory leaks.

Creating Contexts

You can create contexts using the following functions from the context package:

  • context.Background(): Returns a non-nil, empty context. It is typically used as the top-level context.
  • context.TODO(): Returns a non-nil, empty context that can be used when you're not sure which context to use.

Example: Using Background Context

Here’s a simple example demonstrating how to create and use a background context:

package main

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

func main() {
    ctx := context.Background()
    fmt.Println("Starting with background context")

    // Simulate a long-running operation
    longRunningOperation(ctx)
}

func longRunningOperation(ctx context.Context) {
    // Simulate work
    time.Sleep(2 * time.Second)
    fmt.Println("Long-running operation completed")
}

Context with Timeout

To manage timeouts, you can create a context with a deadline using context.WithTimeout. This is particularly useful when you want to ensure that an operation does not run indefinitely.

Example: Using Context with Timeout

package main

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

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel() // Ensure resources are cleaned up

    fmt.Println("Starting operation with timeout")

    err := longRunningOperation(ctx)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Operation completed successfully")
    }
}

func longRunningOperation(ctx context.Context) error {
    select {
    case <-time.After(2 * time.Second):
        return nil // Simulate successful completion
    case <-ctx.Done():
        return ctx.Err() // Return the context error (timeout)
    }
}

In this example, the longRunningOperation function simulates a long-running task. If it takes longer than the specified timeout, it returns a timeout error.

Context with Cancellation

In addition to timeouts, you can also create contexts that can be cancelled manually using context.WithCancel. This allows you to signal goroutines to stop working when they are no longer needed.

Example: Using Context with Cancellation

package main

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

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // Clean up resources

    go longRunningOperation(ctx)

    // Simulate some work in the main goroutine
    time.Sleep(1 * time.Second)
    cancel() // Cancel the context

    time.Sleep(1 * time.Second) // Give the goroutine time to finish
}

func longRunningOperation(ctx context.Context) {
    for {
        select {
        case <-time.After(500 * time.Millisecond):
            fmt.Println("Working...")
        case <-ctx.Done():
            fmt.Println("Operation cancelled")
            return
        }
    }
}

In this example, the longRunningOperation function continuously performs work until it receives a cancellation signal from the context. The main goroutine simulates some work before cancelling the context.

Context Values

Contexts can also carry values that can be accessed by functions that receive the context. This is useful for passing request-scoped data, such as authentication tokens or user IDs.

Example: Using Context Values

package main

import (
    "context"
    "fmt"
)

func main() {
    ctx := context.WithValue(context.Background(), "userID", 42)

    printUserID(ctx)
}

func printUserID(ctx context.Context) {
    userID := ctx.Value("userID")
    fmt.Printf("User ID: %v\n", userID)
}

In this example, we store a user ID in the context and retrieve it in the printUserID function. This pattern helps to pass data without modifying function signatures.

Best Practices

  1. Use Context for Cancellation: Always use context to manage cancellation signals for goroutines that may run indefinitely or for long operations.
  2. Avoid Context Values for Passing Parameters: Use context values sparingly and only for request-scoped data. Avoid using them for passing parameters that can be passed directly to functions.
  3. Always Cancel: Ensure that you call the cancel function returned by context.WithCancel or context.WithTimeout to free resources.

Conclusion

The context package is a powerful tool in Go that helps manage timeouts, cancellation, and request-scoped data. By following best practices and using context effectively, you can build more robust and maintainable Go applications.


Learn more with useful resources