go语言context包

在 Go 语言中,context 包是用于管理请求生命周期、取消操作和传递请求范围数据的核心工具。提供了一种优雅地处理请求范围的数据、取消信号和截止时间(deadline)的机制。context 在并发编程中尤为重要,特别是在处理多个 goroutine 时,能够有效地管理和传递上下文信息,确保资源的合理释放和程序的健壮性。

主要功能

  1. 取消信号(Cancellation)​ :通过 context 可以在多个 goroutine 之间传递取消信号,以便在需要时提前终止操作。
  2. 截止时间(Deadline)​ :可以为 context 设置一个截止时间,超过该时间后自动取消上下文。
  3. 请求范围的值(Request-scoped Values)​ :可以在 context 中存储键值对,方便在处理链中共享数据。

常用的 context 类型

  • context.Background(): 返回一个空的 Context,通常作为根上下文使用。
  • context.TODO(): 返回一个空的 Context,用于暂时不确定应该使用哪个上下文的情况。

创建带有取消功能的 Context

使用 context.WithCancel

go 复制代码
package main

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

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

    go func() {
        // 模拟某个操作
        fmt.Println("hello world")
        time.Sleep(2 * time.Second)
        cancel() // 取消上下文
    }()

    // 监听取消信号
    select {
    case <-ctx.Done():
        fmt.Println("上下文被取消:", ctx.Err())
    case <-time.After(5 * time.Second):
        fmt.Println("操作完成")
    }
}

输出:​

makefile 复制代码
上下文被取消: context canceled

使用 context.WithDeadline

css 复制代码
package main

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

func main() {
    deadline := time.Now().Add(2 * time.Second)
    ctx, cancel := context.WithDeadline(context.Background(), deadline)
    defer cancel()

    select {
    case <-ctx.Done():
        fmt.Println("上下文到达截止时间:", ctx.Err())
    case <-time.After(3 * time.Second):
        fmt.Println("操作完成")
    }
}

输出:​

makefile 复制代码
上下文到达截止时间: context deadline exceeded

使用 context.WithTimeout

context.WithTimeoutcontext.WithDeadline 的简化版本,用于设置超时时间。

css 复制代码
package main

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

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    select {
    case <-ctx.Done():
        fmt.Println("上下文超时:", ctx.Err())
    case <-time.After(3 * time.Second):
        fmt.Println("操作完成")
    }
}

输出:​

makefile 复制代码
上下文超时: context deadline exceeded

Context 中存储值

可以使用 context.WithValue 在上下文中存储键值对,方便在处理链中共享数据。

go 复制代码
package main

import (
    "context"
    "fmt"
)

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

    process(ctx)
}

func process(ctx context.Context) {
    userID := ctx.Value("userID")
    fmt.Println("用户ID:", userID)
}

输出:​

makefile 复制代码
用户ID: 12345

注意事项:​

  • context 中存储的值应该是请求范围内的数据,避免存储过多的全局状态。
  • 使用不可比较的类型作为键(如切片、映射等)会导致运行时错误,建议使用自定义类型。
go 复制代码
type key int

const userIDKey key = 0

ctx := context.WithValue(context.Background(), userIDKey, 12345)

在实际应用中的使用

context 通常用于处理 HTTP 请求、数据库查询等需要超时控制或取消操作的场景。例如,在处理 HTTP 请求时,可以将 http.RequestContext 传递给下游函数,以便在请求被取消时,及时终止相关操作。

go 复制代码
package main

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

func main() {
	
	http.HandleFunc("/", handler)
	server := &http.Server{Addr: ":8080"}

	go func() {
		if err := server.ListenAndServe(); err != nil {
			// 处理错误
			fmt.Println(err)
			os.Exit(100)
		}

	}()
	fmt.Println("服务器启动成功")
	// 模拟服务器运行一段时间后关闭
	time.Sleep(10 * time.Second)
	server.Shutdown(context.Background())
}
func handler(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
/*
	go func() {
		select {
		case <-ctx.Done():
			fmt.Println("请求被取消:", ctx.Err())
		}
	}()
*/
	// 模拟耗时操作
	time.Sleep(time.Second * 1)
	select {
	case <-ctx.Done():
		fmt.Println("请求在第一秒被取消:", ctx.Err())
		return
	default:
	}
	time.Sleep(time.Second * 1)
	select {
	case <-ctx.Done():
		fmt.Println("请求在第二秒被取消:", ctx.Err())
		return
	default:
	}
	time.Sleep(time.Second * 1)
	select {
	case <-ctx.Done():
		fmt.Println("请求在第三秒被取消:", ctx.Err())
		return
	default:
	}
	time.Sleep(time.Second * 1)
	select {
	case <-ctx.Done():
		fmt.Println("请求在第四秒被取消:", ctx.Err())
		return
	default:
	}

	fmt.Fprintln(w, "响应完成")
	fmt.Println("后台打印响应结束")
}

在上述示例中,当客户端取消请求时,ctx.Done() 会被触发,从而可以及时终止耗时操作,释放资源。

最佳实践

  1. 传递 Context 作为函数参数 :尽量将 Context 作为第一个参数传递给函数,命名通常为 ctx

  2. 不要存储 Context 在结构体中Context 应该作为显式的参数传递,而不是隐式地存储在结构体中。

  3. 使用 context.Background() 作为根上下文 :在没有父上下文的情况下,使用 context.Background() 作为根上下文。

  4. 及时取消 Context :对于可以取消的 Context(如 WithCancelWithTimeout),确保在不需要时及时调用取消函数 cancel(),以释放资源。

  5. 检查 ctx.Done()

    • 在长时间运行的操作中定期检查 ctx.Done(),以便及时响应取消或超时。
  6. 避免滥用 WithValue

    • WithValue 仅用于传递请求范围的数据,避免传递大型对象或频繁使用的数据。
  7. 使用 defer cancel()

    • 使用 defer cancel() 确保 context 在函数结束时被取消,避免资源泄漏。
  8. 处理 ctx.Err()

    • 在操作完成后检查 ctx.Err(),以确定操作是否被取消或超时。

总结

context 包在 Go 的并发编程中扮演着重要角色,提供了一种统一的方式来管理取消信号、截止时间和请求范围内的值。合理使用 context 可以提高程序的可维护性和健壮性,特别是在处理复杂的并发场景时尤为重要。

相关推荐
郭京京2 小时前
go语言os.Signal接收操作系统发送的信号的通道
go
smallyu7 小时前
Go 语言 GMP 调度器的原理是什么
后端·go
ERP老兵_冷溪虎山7 小时前
GoLand 卡成幻灯片?Gopher 必藏的 vmoptions 调优表(续集:WebStorm 飞升后,轮到 Go 开发神器起飞)
后端·go
江湖十年9 小时前
万字长文:彻底掌握 Go 1.23 中的迭代器——原理篇
后端·面试·go
程序员爱钓鱼9 小时前
Go语言实战案例-实现分页查询接口
后端·google·go
狼爷1 天前
生产环境慎用 context.Background ():你的系统可能在 “空转”
go
Code_Artist1 天前
[Go]结构体实现接口类型静态校验——引用类型和指针之间的关系
后端·面试·go
郭京京1 天前
go操作mysql数据库(database/sql)
go
郭京京1 天前
go小项目-实现雪花算法
go