【Go - context 速览,场景与用法】

作用

context字面意思上下文,用于关联管理上下文,具体有如下几个作用

  1. 取消信号传递:可以用来传递取消信号,让一个正在执行的函数知道它应该提前终止。
  2. 超时控制:可以设定一个超时时间,自动取消超过执行时间的操作。
  3. 截止时间:与超时类似,但是是设定一个绝对时间点,而不是时间段。
  4. 值传递:可以安全地在请求的上下文中传递数据,避免了使用全局变量或者参数列表不断增长。

由上述看出,context有个重要用途,控制取消

场景与用法

示例1:HTTP 请求处理

在处理 HTTP 请求时,可以为每个请求创建一个 context,用于控制请求处理的整个生命周期。如果请求被取消或超时,依赖该 context 的操作也会被取消。

代码示例

go 复制代码
package main

import (
	"context"
	"fmt"
	"math/rand"
	"net/http"
	"time"
)

func randomSleepAtMost2s() {
	rand.Seed(time.Now().UnixNano()) // 初始化随机数种子
	// 生成 0 到 2000 之间的随机整数(毫秒)
	randomMillis := rand.Intn(2000)
	// 转换为 time.Duration 类型,并乘以 time.Millisecond
	sleepDuration := time.Duration(randomMillis) * time.Millisecond
	// 随机睡眠
	fmt.Println("sleeping for", sleepDuration)
	time.Sleep(sleepDuration)
}

func handler(w http.ResponseWriter, r *http.Request) {
	// 超时时间1s
	ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
	defer cancel()

	// 创建一个模拟正常处理完成的通道
	done := make(chan struct{})

	// 模拟异步处理逻辑
	go func() {
		// 模拟耗时操作
		// - 当随机睡眠超过1s时,会触发 ctx.Done(),取消请求
		// - 当随机睡眠不超过1s时,则会正常处理请求
		randomSleepAtMost2s()
		fmt.Println("request processed")
		close(done) // 处理完成,关闭通道
	}()

	// 模拟耗时操作
	select {
	case <-done:
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("request processed successfully"))
	case <-ctx.Done():
		fmt.Println("request cancelled")
		http.Error(w, "request cancelled", http.StatusRequestTimeout)
		return
	}
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

示例2 :数据库操作

数据库查询或操作可以接受一个 context 参数,允许查询在超时或被取消时立即停止,避免无谓的数据库资源占用。

代码示例

go 复制代码
package main

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

	_ "github.com/lib/pq" // 假设使用 PostgreSQL
)

func queryWithTimeout(ctx context.Context, db *sql.DB, query string) (*sql.Rows, error) {
	// 设置1s的超时时间
	ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
	defer cancel()

	return db.QueryContext(ctx, query)
}

func main() {
	// 连接数据库(示例)
	db, err := sql.Open("postgres", "your_connection_string")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	ctx := context.Background()
	_, err = queryWithTimeout(ctx, db, "SELECT * FROM your_table")
	if err != nil {
		fmt.Println("Query failed:", err)
	}
}

示例3:取消协程

在启动多个 goroutine 进行并发操作时,可以通过 context 控制这些 goroutine 的生命周期,确保它们能够在必要时被正确取消。

代码示例

go 复制代码
package main

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

func operation(ctx context.Context, id int) {
	select {
	case <-time.After(2 * time.Second):
		fmt.Printf("Operation %d completed\n", id)
	case <-ctx.Done():
		fmt.Printf("Operation %d cancelled\n", id)
	}
}

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

	for i := 0; i < 5; i++ {
		go operation(ctx, i)
	}

	time.Sleep(1 * time.Second)
	cancel()                    // 取消所有协程操作
	time.Sleep(3 * time.Second) // 等待足够的时间以打印完日志,观察效果
}

示例4:跨服务调用

在微服务架构中,一个服务调用另一个服务时,可以通过 context 传递关于原始请求的信息,如请求ID,以便进行链路追踪。

代码示例

go 复制代码
package main

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

func callService(ctx context.Context, url string) {
	req, _ := http.NewRequest("GET", url, nil)
	req = req.WithContext(ctx)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Request failed:", err)
		return
	}
	defer resp.Body.Close()

	fmt.Println("Response status:", resp.Status)
}

func main() {
	ctx := context.Background()
	ctx = context.WithValue(ctx, "RequestID", "abc123")
	ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
	defer cancel()

	callService(ctx, "http://example.com")
}

结语

通过这些场景和用法,可以看出 context 在 Go 中的重要性,特别是在需要控制管理请求生命周期时(控制取消)。

相关推荐
weixin_307779133 分钟前
C++代码实现MATLAB中的ode23tb函数功能
开发语言·c++·算法·matlab
LEE7 分钟前
前端转型全栈 03:接口失败也返回 200,OpenAPI 契约与错误码怎么定
前端·后端·全栈
2601_9669496514 分钟前
为什么量化回测中 for 循环比 DataFrame 慢三个数量级?从向量化原理讲起
开发语言·python·pandas·股票数据·quantdash
打工仔折腾 AI36 分钟前
用 Docker 部署 Excalidraw 手绘白板并配置固定公网访问的完整实践
人工智能·后端·python
山岚的运维笔记38 分钟前
mysql 专业笔记 -- 第 36 章:MySQL 管理
运维·数据库·笔记·后端·mysql·oracle·dba
掘金者阿豪41 分钟前
Oracle 迁移金仓,50 个存储过程到底要改多少?聊聊实际改造中的几个问题
后端
大白801 小时前
iOS 内存深度解析:ARC 到底是怎么管理对象生命周期的
后端
焦玉全1 小时前
Redis Lua 脚本实战:从入门到精通(Java 版)
后端
知识的搬运工旺仔1 小时前
唯一索引与 NULL 值:PostgreSQL 主键约束与 NULLS NOT DISTINCT
数据库·后端·sql·postgresql
大勇前进2 小时前
从 OC 到 Swift,老 iOS 开发者踩过的 10 个语法大坑
后端