Skip to content

archlinux 安装笔记 #25

Description

@snxq

Golang 中的 Context

在 Golang 中,Context 是一个非常重要的概念,它用于在 goroutine 之间传递请求范围的值、取消信号和截止时间。Context 包是在 Go 1.7 版本中引入的,它提供了一种优雅的方式来处理并发程序中的取消操作和超时控制。

Context 的基本概念

Context 是一个接口,定义了四个方法:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
  • Deadline(): 返回 context 的截止时间
  • Done(): 返回一个 channel,当 context 被取消时会关闭
  • Err(): 返回 context 被取消的原因
  • Value(): 返回与 key 关联的值

Context 的类型

1. Background Context

context.Background() 是所有 Context 的根,它永远不会被取消,没有值,也没有截止时间。通常用作主函数、初始化和测试的起点。

ctx := context.Background()

2. TODO Context

context.TODO() 当不确定使用哪个 Context 或者还没有可用的 Context 时使用。

ctx := context.TODO()

3. WithCancel

context.WithCancel() 返回一个可取消的 Context 和一个取消函数。

ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保释放资源

4. WithTimeout

context.WithTimeout() 返回一个带有超时的 Context。

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

5. WithDeadline

context.WithDeadline() 返回一个带有截止时间的 Context。

deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()

6. WithValue

context.WithValue() 返回一个携带键值对的 Context。

ctx := context.WithValue(context.Background(), "userID", 12345)

使用示例

超时控制

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    select {
    case <-time.After(3 * time.Second):
        fmt.Println("操作完成")
    case <-ctx.Done():
        fmt.Println("操作超时:", ctx.Err())
    }
}

HTTP 请求中的使用

func fetchData(ctx context.Context, url string) error {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return err
    }
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    // 处理响应
    return nil
}

传递值

type key string

const userIDKey key = "userID"

func main() {
    ctx := context.WithValue(context.Background(), userIDKey, "user123")
    processRequest(ctx)
}

func processRequest(ctx context.Context) {
    userID := ctx.Value(userIDKey)
    if userID != nil {
        fmt.Printf("处理用户 %s 的请求\n", userID)
    }
}

最佳实践

  1. 总是将 Context 作为第一个参数传递

    func DoSomething(ctx context.Context, arg1 string, arg2 int) error {
        // ...
    }
  2. 不要将 Context 存储在结构体中
    Context 应该通过函数参数传递,而不是存储在结构体中。

  3. 不要传递 nil Context
    如果不确定使用哪个 Context,使用 context.TODO()。

  4. Context 的 Value 方法应该仅用于传递请求范围的数据
    不要用它来传递可选参数。

  5. 及时调用 cancel 函数
    使用 defer cancel() 确保资源被释放。

注意事项

  • Context 是并发安全的,可以在多个 goroutine 中安全使用
  • Context 的取消是级联的,父 Context 取消时,所有子 Context 也会被取消
  • 不要忽略 Context 的 Done channel,它是取消信号的重要来源
  • Context 的 Value 方法查找是递归的,会向上查找父 Context

Context 是 Go 并发编程中的重要工具,正确使用它可以让你的程序更加健壮和高效。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions