Golang中是否应该对所有调用使用相同的context.Background()?

Golang中是否应该对所有调用使用相同的context.Background()? 我有一个使用 golang1.13 和 gorilla/mux v1.7.3 编写的(生产环境)REST API。

目前我实际上并不需要请求取消或超时功能,所以每次我需要向某个函数传递上下文时,我只是使用包中定义的同一个 ctx 变量,它在 func init() 中定义如下:

ctx := context.Background()

例如,我有一个使用 Azure SDK 执行某些功能的包。Azure SDK 的每个方法都需要传递一个上下文,所以我传递上面提到的 ctx。

因此,我的 REST API 的每个请求有时会使用上述功能。

  1. 我不明白这到底是好是坏,或者是否被认为是好的/坏的做法。
  2. 最重要的问题是——目前这似乎可以正常工作。它会不会出问题?

更多关于Golang中是否应该对所有调用使用相同的context.Background()?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

context.Background 的源代码 可以看出,它只是

type emptyCtx int

的一个实例,默认值为 0。正如文档所述,这样的上下文不执行任何操作。

请注意,还有 context.TODO

当不清楚该使用哪个 Context 时,代码应使用 context.TODO

尽管 context.Backgroundcontext.TODO 是相同类型的实例,但对于您的使用场景,使用 context.TODO 似乎更为合适。

我认为使用这两者中的任何一个都不会出错。它们只是不执行任何操作。

更多关于Golang中是否应该对所有调用使用相同的context.Background()?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言中,对所有调用使用同一个context.Background()实例是不推荐的,尤其是在生产环境的REST API中。虽然当前可能正常工作,但这会带来潜在的问题。

主要问题

  1. 无法实现请求级控制:每个HTTP请求应该有自己的上下文链,用于管理该请求的生命周期
  2. 资源泄漏风险:当请求被取消时,无法传播取消信号到依赖的操作
  3. 缺乏超时控制:无法为不同的操作设置适当的超时时间

正确做法示例

在HTTP处理器中,应该使用请求的上下文:

package main

import (
    "context"
    "net/http"
    "time"
    
    "github.com/gorilla/mux"
    "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
)

// 错误的做法 - 使用全局context
var globalCtx = context.Background()

// 正确的做法 - 使用请求上下文
func handlerWithProperContext(w http.ResponseWriter, r *http.Request) {
    // 使用请求的上下文作为基础
    ctx := r.Context()
    
    // 可以为特定操作设置超时
    timeoutCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
    defer cancel()
    
    // 将带有超时的上下文传递给Azure SDK
    client := compute.NewVirtualMachinesClient("<subscriptionID>")
    future, err := client.CreateOrUpdate(timeoutCtx, "<resourceGroup>", "<vmName>", compute.VirtualMachine{})
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    // 其他操作...
}

// 更完整的示例
func azureOperationHandler(w http.ResponseWriter, r *http.Request) {
    // 从请求获取上下文
    ctx := r.Context()
    
    // 为数据库操作设置更短的超时
    dbCtx, dbCancel := context.WithTimeout(ctx, 5*time.Second)
    defer dbCancel()
    
    // 执行数据库操作
    result, err := dbQuery(dbCtx, "SELECT * FROM users")
    if err != nil {
        if dbCtx.Err() == context.DeadlineExceeded {
            http.Error(w, "Database timeout", http.StatusGatewayTimeout)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    // 为外部API调用设置不同的超时
    apiCtx, apiCancel := context.WithTimeout(ctx, 10*time.Second)
    defer apiCancel()
    
    // 调用Azure SDK
    err = callAzureAPI(apiCtx, result)
    if err != nil {
        if apiCtx.Err() == context.DeadlineExceeded {
            http.Error(w, "Azure API timeout", http.StatusGatewayTimeout)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Success"))
}

func dbQuery(ctx context.Context, query string) (interface{}, error) {
    // 模拟数据库查询
    select {
    case <-time.After(3 * time.Second):
        return "data", nil
    case <-ctx.Done():
        return nil, ctx.Err()
    }
}

func callAzureAPI(ctx context.Context, data interface{}) error {
    // 模拟Azure API调用
    select {
    case <-time.After(8 * time.Second):
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/api/operation", azureOperationHandler)
    
    http.ListenAndServe(":8080", r)
}

上下文传播的重要性

当客户端取消请求时,正确的上下文传播可以确保所有相关操作也被取消:

func handleLongOperation(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    
    // 创建带有值的上下文
    ctx = context.WithValue(ctx, "requestID", "12345")
    
    // 启动多个goroutine,共享同一个上下文
    done := make(chan bool)
    
    go func() {
        select {
        case <-time.After(5 * time.Second):
            // 长时间操作
        case <-ctx.Done():
            // 如果请求被取消,这里会立即返回
            log.Println("Operation cancelled:", ctx.Err())
            return
        }
        done <- true
    }()
    
    select {
    case <-done:
        w.Write([]byte("Completed"))
    case <-ctx.Done():
        // 客户端断开连接或取消请求
        log.Println("Request cancelled by client")
        return
    }
}

使用全局context.Background()会破坏Go的上下文机制,使得请求生命周期管理、取消传播和超时控制都无法正常工作。即使当前不需要这些功能,也应该遵循正确的模式,以便未来扩展和维护。

回到顶部