Golang学习笔记:context的使用场景

context在Go语言中是一个非常重要的知识点,在使用场景和面试问答中经常被提及到,如果想学习其原理知识,可以参考, 这篇文章只做使用场景总结。

context包为协程间传递信号提供了一些方法,用于管理请求生命周期和控制并发操作,context的使用场景有以下几个:

控制请求的生命周期

在处理HTTP请求时,通常需要确保请求处理过程中能够及时取消、超时或结束。尤其当请求涉及到多个下游服务调用时,若一个服务响应缓慢或失败,必须取消所有其他正在进行的操作。

示例:

复制代码
func handler(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	resultChan := make(chan string, 1) // 使用缓冲通道避免 goroutine 泄漏

	// 启动 goroutine 执行耗时操作
	go func() {
		// 模拟耗时操作
		select {
		case <-time.After(2 * time.Second):
			// 检查父 context 是否已经结束
			select {
			case resultChan <- "operation completed successfully":
				log.Println("Operation completed and result sent")
			case <-ctx.Done():
				log.Println("Operation completed but context was canceled, discarding result")
			}
		case <-ctx.Done():
			log.Println("Operation canceled before completion")
			// 不发送结果,因为 context 已取消
		}
	}()

	// 等待结果或取消信号
	select {
	case <-ctx.Done():
		// 请求取消或超时
		switch ctx.Err() {
		case context.Canceled:
			log.Println("Request was canceled by client")
			http.Error(w, "request canceled", http.StatusRequestTimeout)
		case context.DeadlineExceeded:
			log.Println("Request timeout")
			http.Error(w, "request timeout", http.StatusGatewayTimeout)
		default:
			log.Printf("Context error: %v", ctx.Err())
			http.Error(w, "request failed", http.StatusInternalServerError)
		}
		return
	case result := <-resultChan:
		// 正常返回结果
		log.Println("Returning successful result")
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		fmt.Fprintln(w, result)
	}
}

处理超时和截止时间

当处理需要网络调用或长时间运行的操作时,设定一个超时时间或截止日期是很重要的。context可以传递一个超时或截止日期,自动取消操作,避免资源浪费。

示例:

复制代码
package main

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

func main() {
	ctx := context.Background()
	fmt.Println(fetchData(ctx))
}

func fetchData(ctx context.Context) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()

	ch := make(chan string, 1)
	go func() {
		//模拟耗时操作
		time.Sleep(3 * time.Second)
		ch <- "data"
	}()

	select {
	case <-ctx.Done():
		fmt.Println("Done")
		return "", ctx.Err()
	case result := <-ch:
		fmt.Println("result")
		return result, nil
	}
}

package main

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

func main() {
	// 设置一个具体的截止时间
	deadline := time.Now().Add(3 * time.Second)
	ctx, cancel := context.WithDeadline(context.Background(), deadline)
	defer cancel() // 重要:确保资源被释放

	// 检查截止时间
	if dl, ok := ctx.Deadline(); ok {
		fmt.Printf("Deadline set to: %v\n", dl.Format("15:04:05.000"))
	}

	// 等待 context 超时
	select {
	case <-time.After(5 * time.Second):
		fmt.Println("Operation completed")
	case <-ctx.Done():
		fmt.Printf("Context canceled: %v\n", ctx.Err())
	}
}

传递元数据

在微服务架构中,需要在服务之间传递一些与请求相关的元数据,例如认证信息、分布日志ID等,context提供了传递这些信息的方式。

示例:

复制代码
func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, "requestID", "12345")

    processRequest(ctx)
}

func processRequest(ctx context.Context) {
    reqID := ctx.Value("requestID")
    fmt.Println("Request ID:", reqID)
}

协同工作

在复杂的并发任务中,不同的协程可能需要相互协作,或需要再特定条件下取消其他协程,context可以用于协同工作,统一管理多个协程的状态。

复制代码
func main() {
    ctx, cancel := context.WithCancel(context.Background())

    go worker(ctx, "worker1")
    go worker(ctx, "worker2")

    time.Sleep(1 * time.Second)
    cancel() // 取消所有工作

    time.Sleep(1 * time.Second)
}

func worker(ctx context.Context, name string) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println(name, "stopped")
            return
        default:
            fmt.Println(name, "working")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

限制并发数量

在特定场景下,需要限制并发执行的协程数量,避免过度消耗系统资源,context可以与信号量或sync.WaitGroup一起使用来限制并发数量。

复制代码
func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    sem := make(chan struct{}, 3) // 限制并发数为3
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            sem <- struct{}{} // 获取信号
            defer func() { <-sem }() // 释放信号

            worker(ctx, i)
        }(i)
    }

    wg.Wait()
}

func worker(ctx context.Context, id int) {
    select {
    case <-ctx.Done():
        fmt.Printf("worker %d canceled\n", id)
        return
    default:
        fmt.Printf("worker %d working\n", id)
        time.Sleep(1 * time.Second)
    }
}
相关推荐
知识分享小能手3 小时前
微信小程序入门学习教程,从入门到精通,微信小程序页面交互 —— 知识点详解与案例实现(3)
前端·javascript·学习·react.js·微信小程序·小程序·交互
charlie1145141913 小时前
精读C++20设计模式——结构型设计模式:代理模式
c++·学习·设计模式·代理模式·c++20·概论
武昌库里写JAVA3 小时前
Java 设计模式在 Spring 框架中的实践:工厂模式与单例模式
java·vue.js·spring boot·sql·学习
峥嵘life3 小时前
Android16 adb投屏工具Scrcpy介绍
android·开发语言·python·学习·web安全·adb
tiankongdeyige3 小时前
Unity学习之垃圾回收GC
学习
特种加菲猫4 小时前
Linux之线程池
linux·笔记
DKPT4 小时前
JVM如何管理直接内存?
java·笔记·学习
前路不黑暗@5 小时前
Java:代码块
java·开发语言·经验分享·笔记·python·学习·学习方法
序属秋秋秋5 小时前
《C++进阶之C++11》【可变参数模板 + emplace接口 + 新的类功能】
c++·笔记·学习·c++11·可变参数模板·emplace系列接口