searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

【Go语言】context详解

2024-12-11 08:57:56
0
0

一、起源

context包最初由Go语言的核心团队提出,并于Go 1.7版本正式引入。其设计灵感来源于Google内部的实践,旨在解决长期运行的并发任务和服务之间如何协调取消信号和超时控制的问题。在早期的Go语言编程中,开发者通常使用全局变量或者依赖参数传递来实现上下文管理,这种方式在多层嵌套和并发执行时,容易出现代码可读性差、维护困难等问题。因此,Go语言的设计者决定创建一个标准化的解决方案,context应运而生。

二、主要作用

context的主要作用是在并发操作中传递控制信号,特别是在涉及多个goroutine的操作时,它提供了以下几个核心功能:

  1. 取消信号(Cancellation):通过context.CancelFunc,Go语言允许父任务通知其子任务取消操作,这对于清理资源、停止不再需要的任务、避免内存泄漏等至关重要。

  2. 超时控制(Timeout/Deadline):通过context.WithTimeoutcontext.WithDeadline,开发者可以设置任务的最大执行时间,当超时到达时,context会自动触发取消信号,帮助避免长时间挂起的操作。

  3. 跨goroutine的共享数据(Request-scoped Data)context允许在多个goroutine之间传递元数据,特别是在一个请求生命周期内共享的值,这对于记录日志、追踪请求ID或传递认证信息等非常有用。

  4. 避免资源泄漏:通过控制超时和取消操作,context有助于及时清理资源,避免因未取消的操作或长期挂起的goroutine造成系统负担。

三、使用方式

1. 引入 context 包

在 Go 中使用 context,首先需要引入 context 包:

import "context"

2. 创建一个 context

context 的创建通常通过 context.Background()context.TODO() 方法来完成。

  • context.Background():一般用作主程序的顶层 context,通常在主函数中使用。
  • context.TODO():当你不确定该使用什么 context 时,可以使用 context.TODO()。通常用于暂时的占位符。

示例:

ctx := context.Background()

3. 创建具有取消功能的 context

context 包提供了 WithCancelWithTimeoutWithDeadline 等函数来创建具有特定功能的 context

3.1 使用 context.WithCancel

context.WithCancel 用于创建一个可以手动取消的 context。它返回一个子 context 和一个取消函数 CancelFunc,你可以调用取消函数来取消该 context,从而通知相关的 goroutine 停止执行。

示例:

ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保在函数结束时取消

// 假设在 goroutine 中运行的任务
go func(ctx context.Context) {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Println("任务被取消:", ctx.Err())
    }
}(ctx)

// 取消操作
time.Sleep(2 * time.Second) // 模拟一段时间后取消
cancel()

3.2 使用 context.WithTimeout

context.WithTimeout 用于设置一个超时期限,当超时到达时,会自动取消该 context

示例:

 
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // 确保在函数结束时取消

go func(ctx context.Context) {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Println("任务超时:", ctx.Err())
    }
}(ctx)

在这个例子中,任务将在 2 秒后被取消,因为超时设置为 2 秒。

3.3 使用 context.WithDeadline

context.WithDeadline 用于创建一个具有特定截止时间的 context。当达到截止时间时,context 会自动被取消。

示例:

deadline := time.Now().Add(2 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel() // 确保在函数结束时取消

go func(ctx context.Context) {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Println("任务到期:", ctx.Err())
    }
}(ctx)

4. 传递 context 给子 goroutine

通常,context 会在父 goroutine 中创建,并传递给子 goroutine。这样,子 goroutine 可以根据父 goroutine 传递的 context 来判断任务是否被取消、是否超时,或者共享一些数据。

示例:

 
func doTask(ctx context.Context) {
    select {
    case <-time.After(3 * time.Second):
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Println("任务被取消:", ctx.Err())
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    go doTask(ctx)

    time.Sleep(1 * time.Second) // 等待一段时间
    cancel() // 取消任务
}

5. 在 context 中传递数据

context 允许在多个 goroutine 之间传递请求相关的数据(例如,请求 ID 或认证信息)。可以通过 context.WithValue 创建一个新的 context,并将键值对数据存储在 context 中。

注意:不要用 context 传递大数据或敏感信息,因为它的主要目的是携带跨 API 边界和跨 goroutine 的请求级别的数据。

示例:

 
type key string

const UserIDKey key = "userID"

func doTask(ctx context.Context) {
    userID := ctx.Value(UserIDKey)
    fmt.Println("UserID:", userID)
}

func main() {
    ctx := context.Background()

    // 将数据传递给上下文
    ctx = context.WithValue(ctx, UserIDKey, 12345)

    go doTask(ctx)
    time.Sleep(1 * time.Second) // 等待goroutine执行
}

6. context 的取消与清理

每当使用 context.WithCancelcontext.WithTimeoutcontext.WithDeadline 时,记得使用 defer cancel() 来确保在不再需要该 context 时进行清理。这会帮助避免泄漏和过度消耗资源。

7. context 的嵌套与传递

context 是可以嵌套的。当你通过 WithCancelWithTimeout 等函数创建子 context 时,子 context 会继承父 context 的值和状态。如果父 context 被取消或超时,子 context 也会被取消。

示例:

 
func main() {
    ctx := context.Background()

    // 创建一个具有 3 秒超时的子 context
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()

    // 创建另一个子 context,并在其中传递数据
    ctx = context.WithValue(ctx, "userID", 42)

    go func(ctx context.Context) {
        select {
        case <-time.After(5 * time.Second):
            fmt.Println("任务完成")
        case <-ctx.Done():
            fmt.Println("任务被取消:", ctx.Err())
            userID := ctx.Value("userID")
            fmt.Println("用户 ID:", userID)
        }
    }(ctx)

    time.Sleep(4 * time.Second)
}
在上面的代码中,子 context 会继承父 context 的取消机制,并且传递了用户 ID 的信息。
0条评论
作者已关闭评论
c****k
6文章数
0粉丝数
c****k
6 文章 | 0 粉丝
原创

【Go语言】context详解

2024-12-11 08:57:56
0
0

一、起源

context包最初由Go语言的核心团队提出,并于Go 1.7版本正式引入。其设计灵感来源于Google内部的实践,旨在解决长期运行的并发任务和服务之间如何协调取消信号和超时控制的问题。在早期的Go语言编程中,开发者通常使用全局变量或者依赖参数传递来实现上下文管理,这种方式在多层嵌套和并发执行时,容易出现代码可读性差、维护困难等问题。因此,Go语言的设计者决定创建一个标准化的解决方案,context应运而生。

二、主要作用

context的主要作用是在并发操作中传递控制信号,特别是在涉及多个goroutine的操作时,它提供了以下几个核心功能:

  1. 取消信号(Cancellation):通过context.CancelFunc,Go语言允许父任务通知其子任务取消操作,这对于清理资源、停止不再需要的任务、避免内存泄漏等至关重要。

  2. 超时控制(Timeout/Deadline):通过context.WithTimeoutcontext.WithDeadline,开发者可以设置任务的最大执行时间,当超时到达时,context会自动触发取消信号,帮助避免长时间挂起的操作。

  3. 跨goroutine的共享数据(Request-scoped Data)context允许在多个goroutine之间传递元数据,特别是在一个请求生命周期内共享的值,这对于记录日志、追踪请求ID或传递认证信息等非常有用。

  4. 避免资源泄漏:通过控制超时和取消操作,context有助于及时清理资源,避免因未取消的操作或长期挂起的goroutine造成系统负担。

三、使用方式

1. 引入 context 包

在 Go 中使用 context,首先需要引入 context 包:

import "context"

2. 创建一个 context

context 的创建通常通过 context.Background()context.TODO() 方法来完成。

  • context.Background():一般用作主程序的顶层 context,通常在主函数中使用。
  • context.TODO():当你不确定该使用什么 context 时,可以使用 context.TODO()。通常用于暂时的占位符。

示例:

ctx := context.Background()

3. 创建具有取消功能的 context

context 包提供了 WithCancelWithTimeoutWithDeadline 等函数来创建具有特定功能的 context

3.1 使用 context.WithCancel

context.WithCancel 用于创建一个可以手动取消的 context。它返回一个子 context 和一个取消函数 CancelFunc,你可以调用取消函数来取消该 context,从而通知相关的 goroutine 停止执行。

示例:

ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保在函数结束时取消

// 假设在 goroutine 中运行的任务
go func(ctx context.Context) {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Println("任务被取消:", ctx.Err())
    }
}(ctx)

// 取消操作
time.Sleep(2 * time.Second) // 模拟一段时间后取消
cancel()

3.2 使用 context.WithTimeout

context.WithTimeout 用于设置一个超时期限,当超时到达时,会自动取消该 context

示例:

 
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // 确保在函数结束时取消

go func(ctx context.Context) {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Println("任务超时:", ctx.Err())
    }
}(ctx)

在这个例子中,任务将在 2 秒后被取消,因为超时设置为 2 秒。

3.3 使用 context.WithDeadline

context.WithDeadline 用于创建一个具有特定截止时间的 context。当达到截止时间时,context 会自动被取消。

示例:

deadline := time.Now().Add(2 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel() // 确保在函数结束时取消

go func(ctx context.Context) {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Println("任务到期:", ctx.Err())
    }
}(ctx)

4. 传递 context 给子 goroutine

通常,context 会在父 goroutine 中创建,并传递给子 goroutine。这样,子 goroutine 可以根据父 goroutine 传递的 context 来判断任务是否被取消、是否超时,或者共享一些数据。

示例:

 
func doTask(ctx context.Context) {
    select {
    case <-time.After(3 * time.Second):
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Println("任务被取消:", ctx.Err())
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    go doTask(ctx)

    time.Sleep(1 * time.Second) // 等待一段时间
    cancel() // 取消任务
}

5. 在 context 中传递数据

context 允许在多个 goroutine 之间传递请求相关的数据(例如,请求 ID 或认证信息)。可以通过 context.WithValue 创建一个新的 context,并将键值对数据存储在 context 中。

注意:不要用 context 传递大数据或敏感信息,因为它的主要目的是携带跨 API 边界和跨 goroutine 的请求级别的数据。

示例:

 
type key string

const UserIDKey key = "userID"

func doTask(ctx context.Context) {
    userID := ctx.Value(UserIDKey)
    fmt.Println("UserID:", userID)
}

func main() {
    ctx := context.Background()

    // 将数据传递给上下文
    ctx = context.WithValue(ctx, UserIDKey, 12345)

    go doTask(ctx)
    time.Sleep(1 * time.Second) // 等待goroutine执行
}

6. context 的取消与清理

每当使用 context.WithCancelcontext.WithTimeoutcontext.WithDeadline 时,记得使用 defer cancel() 来确保在不再需要该 context 时进行清理。这会帮助避免泄漏和过度消耗资源。

7. context 的嵌套与传递

context 是可以嵌套的。当你通过 WithCancelWithTimeout 等函数创建子 context 时,子 context 会继承父 context 的值和状态。如果父 context 被取消或超时,子 context 也会被取消。

示例:

 
func main() {
    ctx := context.Background()

    // 创建一个具有 3 秒超时的子 context
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()

    // 创建另一个子 context,并在其中传递数据
    ctx = context.WithValue(ctx, "userID", 42)

    go func(ctx context.Context) {
        select {
        case <-time.After(5 * time.Second):
            fmt.Println("任务完成")
        case <-ctx.Done():
            fmt.Println("任务被取消:", ctx.Err())
            userID := ctx.Value("userID")
            fmt.Println("用户 ID:", userID)
        }
    }(ctx)

    time.Sleep(4 * time.Second)
}
在上面的代码中,子 context 会继承父 context 的取消机制,并且传递了用户 ID 的信息。
文章来自个人专栏
Golang学习
1 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0