去皮卡多 发表于 5 天前

Go语言中的Context

目次

Go语言中的Context
1. Context的根本概念
1.1 Context的核心作用
2. Context的根本用法
2.1 创建Context
背景Context
可取消的Context
带有超时的Context
2.2 在Goroutine间转达Context
2.3 获取Context的值
为Context添加自定义数据
访问Context中的值
3. Context的高级用法
3.1 Context链
3.2 多个Context的选择
3.3 Context的利用规范
4. Context的最佳实践
4.1 在HTTP处置惩罚中利用Context
4.2 在数据库查询中利用Context
4.3 在多层函数调用中转达Context
4.4 利用Context进行资源释放
5. Context的替换方案
5.1 利用通道转达取消信号
5.2 利用 ErrGroup 进行错误处置惩罚
6. 总结

Go语言中的Context

在Go语言中,context是一个重要的功能,用于在多个goroutine之间转达取消信号、超时控制和请求相干的上下文信息。它是Go语言并发编程中的一个关键组件,能够有效地管理差别使命之间的协作和资源释放。本文将详细探究context的功能、用法及其在现实开发中的应用场景。
1. Context的根本概念

context,即上下文,在Go语言中是一个接口,定义了四个方法:CancelFunc, Deadline, Done, 和 Err。它主要用于在差别的goroutine之间转达取消信号和上下文信息。
以下是context.Context接口的定义:
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
1.1 Context的核心作用


[*]取消信号:context可以在父goroutine中创建,并转达给子goroutine。当父goroutine完成时,可以通过调用CancelFunc取消子goroutine的执行。
[*]超时控制:context可以设置一个超时时间,确保子goroutine在指定时间内完成使命,防止无限等待。
[*]上下文信息:context可以携带一些请求相干的信息,比如用户ID、请求ID、开始时间等,方便在差别的goroutine中访问和利用。
2. Context的根本用法

2.1 创建Context

context可以通过context.Background()和context.WithCancel等方法创建。常见的创建方式如下:
背景Context

全部的context都应该从context.Background()开始,这是整个上下文树的根节点。
ctx = context.Background()
可取消的Context

利用context.WithCancel创建一个可取消的context。
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
带有超时的Context

利用context.WithDeadline或context.WithTimeout创建一个带有超时时间的context。
// 使用Deadline
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
defer cancel()

// 使用Timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
2.2 在Goroutine间转达Context

context的计划初志是在线性调用链中转达。当启动一个新的goroutine时,应将context转达给该goroutine。
package main

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

func worker(ctx context.Context) {
    select {
    case <-ctx.Done():
      fmt.Println("Worker: Context已取消")
    case <-time.After(5 * time.Second):
      fmt.Println("Worker: 完成任务")
    }
}

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

    go worker(ctx)

    fmt.Println("Main: 等待5秒后取消Context")
    time.Sleep(3 * time.Second)
    cancel()
    fmt.Println("Main: Context已取消")
}
在上述代码中,main函数和worker函数共享同一个context。当main函数调用cancel()时,worker函数会通过ctx.Done()信号知道已被取消。
2.3 获取Context的值

context还可以携带键值对的数据,通过Value(key interface{})方法获取。
为Context添加自定义数据

利用context.WithValue将自定义数据添加到context中。
ctx = context.WithValue(context.Background(), "requestID", "12345")
访问Context中的值

在须要访问context值的位置,调用Value(key)方法,并传入相应的键。
requestID, ok := ctx.Value("requestID").(string)
if ok {
    fmt.Printf("Request ID: %s\n", requestID)
}
须要注意的是,Value方法返回的是一个interface{}范例,须要进行范例断言才气利用。
3. Context的高级用法

3.1 Context链

context可以形成链条布局,每个子context继续自父context,并添加额外的值或取消操作。
ctxBackground := context.Background()
ctxWithValue := context.WithValue(ctxBackground, "requestID", "12345")
ctxWithCancel := context.WithCancel(ctxWithValue)
在Context链中,子context会继续父context的值,同时也可以有自己的值和取消操作。
3.2 多个Context的选择

在多个context同时存在时,通常须要利用select语句来处置惩罚多个Done()信号。
select {
case <-ctx1.Done():
    handleCancel(ctx1)
case <-ctx2.Done():
    handleCancel(ctx2)
default:
    // 进行其他操作
}
3.3 Context的利用规范


[*]制止作为布局体的字段:context不应该作为布局体的字段,而是应该通过函数参数转达。
[*]不应长时间持有context:context是用于短期的取消和超时控制,不应长时间持有,特别是在函数之间转达。
[*]制止将context存储在全局变量中:全局变量会导致context的生命周期难以控制,增长资源走漏的风险。
[*]利用context管理资源:利用context的Done()信号,释放不再须要的资源,如文件句柄、网络连接等。
4. Context的最佳实践

4.1 在HTTP处置惩罚中利用Context

在处置惩罚HTTP请求时,context可以用来转达请求相干的信息,并在出现错误或超时时实时取消后续操作。
package main

import (
    "context"
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    requestID := ctx.Value("requestID")
    fmt.Printf("处理请求 ID: %s\n", requestID)
    // 处理具体业务逻辑
}
4.2 在数据库查询中利用Context

context可以用于设置数据库查询的超时时间,制止长时间阻塞。
package main

import (
    "context"
    "database/sql"
    "fmt"
    "time"
)

func queryDatabase(ctx context.Context) {
    query := "SELECT * FROM mytable"
    ctxTimeout, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    rows, err := db.QueryContext(ctxTimeout, query)
    if err != nil {
      fmt.Printf("查询失败: %v\n", err)
      return
    }
    defer rows.Close()

    // 处理查询结果
}
4.3 在多层函数调用中转达Context

在多层函数调用中,始终将context作为第一个参数转达,确保取消信号和超时能够准确流传。
package main

import (
    "context"
    "fmt"
)

func outerFunction(ctx context.Context) {
    innerFunction(ctx)
}

func innerFunction(ctx context.Context) {
    // 使用ctx进行操作
    fmt.Println("内层函数: 使用传递过来的Context")
}
4.4 利用Context进行资源释放

通过context的Done()信号,可以在须要时实时释放资源,如关闭文件、断开连接等。
package main

import (
    "context"
    "fmt"
    "os"
)

func processFile(ctx context.Context, filename string) {
    file, err := os.Open(filename)
    if err != nil {
      fmt.Printf("打开文件失败: %v\n", err)
      return
    }

    select {
    case <-ctx.Done():
      fmt.Println("Context取消,关闭文件")
      file.Close()
      return
    default:
      fmt.Println("开始处理文件")
      // 处理文件内容
    }
}
5. Context的替换方案

虽然context是Go语言标准库提供的最佳解决方案,但在某些特定场景下,开发者可能会寻求其他替换方案。以下是几种常见的替换方案:
5.1 利用通道转达取消信号

除了context,开发者还可以通过通道转达取消信号。
package main

import (
    "fmt"
)

func worker(done <-chan struct{}) {
    select {
    case <-done:
      fmt.Println("Worker: 已取消")
    }
}

func main() {
    done := make(chan struct{})
    go worker(done)
    fmt.Println("Main: 等待3秒后取消")
    time.Sleep(3 * time.Second)
    done <- struct{}{}
}
5.2 利用 ErrGroup 进行错误处置惩罚

在处置惩罚多个子使命时,可以利用errgroup.Group来管理每个使命的错误,并在任意一个使命失败时取消整个组。
package main

import (
    "context"
    "fmt"
    "sync/errgroup"
)

func worker(ctx context.Context) error {
    // 执行具体的工作
    return nil
}

func main() {
    ctx := context.Background()
    g, egctx := errgroup.WithContext(ctx)
   
    for i := 0; i < 5; i++ {
      g.Go(func() error {
            return worker(egctx)
      })
    }

    if err := g.Wait(); err != nil {
      fmt.Printf("错误: %v\n", err)
      return
    }
}
6. 总结

context是Go语言中用于在多个goroutine之间转达取消信号、超时控制和上下文信息的重要机制。通过合理利用context,开发者可以更高效地管理并发使命,确保资源的实时释放和步伐的健壮性。在现实开发中,遵循context的利用规范和最佳实践,能够显著提升代码的可维护性和性能。
无论是处置惩罚HTTP请求、数据库查询,还是在多层函数调用中转达信息,context都能发挥其独特的作用。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Go语言中的Context