什么是context

Go语言的Context是一个在并发环境中传递请求范围的值、控制请求的生命周期以及取消请求的机制。它是Go标准库中的一个重要组件,用于解决并发程序中的一些常见问题。

解决了什么问题

  1. 请求范围的值传递:在一个请求处理链中的不同函数、方法或Goroutine之间传递请求相关的值,例如请求ID、用户身份验证信息等。通过Context,可以避免显式地传递参数,简化函数签名和函数调用。

  2. 请求的生命周期控制:在并发处理中,当一个请求被处理时,可能会创建多个Goroutine来执行各种任务。Context提供了一种机制来控制这些Goroutine的生命周期,确保它们在适当的时候退出或停止。

  3. 请求的取消信号:有时需要提前取消一个请求的处理,例如处理超时、用户取消请求等情况。Context提供了一种机制来发送取消信号,通知相关的Goroutine停止处理请求,并释放相关资源。

总结起来,Go的Context通过与goroutine和channel的配合使用,提供了一种简洁而可靠的机制来传递请求范围的值、控制请求的生命周期和取消请求。它解决了并发程序中的一些常见问题,使得编写并发安全和可控制的代码变得更加容易。

实现原理

Context的实现原理主要依赖于Go语言中的两个特性:goroutine和channel。

每个Context实例都与一个goroutine关联,并且可以通过goroutine的调用链传递给其他goroutine。这种关联可以通过Context的派生(使用context.WithCancelcontext.WithDeadline等函数)来实现。当一个Context被取消或到达截止时间时,与之关联的goroutine会收到一个取消信号。

Context的取消信号是通过一个内置的Done通道实现的。当Context被取消时,该通道会关闭,相关的goroutine可以通过监视这个通道来接收取消信号。一旦接收到取消信号,相关的goroutine应该停止处理请求,并释放相关资源。

Context还提供了一个Value方法,用于在不同goroutine之间传递请求范围的键值对。这个方法使用了goroutine安全的数据结构来存储键值对,并且可以在整个调用链中进行传递和访问。

Context接口

// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key interface{}) interface{}
}

实战

Deadline

超时cancel

package main

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

func main() {
	now := time.Now()

	// 创建5秒后超时时间的context
	// 5秒之后内部自动执行cancel方法
	ctx, cancel := context.WithDeadline(context.Background(), now.Add(time.Second*5))

	// 启动goroutine调用服务
	go callService(ctx)

	time.Sleep(10 * time.Second)

	// 主动调用cancel
	cancel()

	select {
	case <-ctx.Done():
		fmt.Println("收到context的canceled信号,退出主服务,time=", time.Now())
	}
}

// callService 模拟耗时任务
func callService(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("子服务收到context的canceled信号,退出服务,time=", time.Now())
			return
		default:
			fmt.Println("继续执行子服务逻辑,time=", time.Now())
			time.Sleep(1 * time.Second)
		}
	}
}

超时之前cancel

package main

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

func main() {
	now := time.Now()

	// 创建5秒后超时时间的context
	// 5秒之后内部自动执行cancel方法
	ctx, cancel := context.WithDeadline(context.Background(), now.Add(time.Second*10))

	// 启动goroutine调用服务
	go callService(ctx)

	time.Sleep(5 * time.Second)

	// 主动调用cancel
	cancel()

	select {
	case <-ctx.Done():
		fmt.Println("收到context的canceled信号,退出主服务,time=", time.Now())
	}
}

// callService 模拟耗时任务
func callService(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("子服务收到context的canceled信号,退出服务,time=", time.Now())
			return
		default:
			fmt.Println("继续执行子服务逻辑,time=", time.Now())
			time.Sleep(1 * time.Second)
		}
	}
}

Timeout

超时cancel

package main

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

func main() {
	// 创建5秒后超时时间的context
	// 5秒之后内部自动执行cancel方法
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)

	// 启动goroutine调用服务
	go callService(ctx)

	time.Sleep(10 * time.Second)

	// 主动调用cancel,ctx在这之前已经调用cancel
	cancel()
}

// callService 模拟耗时任务
func callService(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("收到context的canceled信号,退出服务,time=", time.Now())
			return
		default:
			fmt.Println("继续执行服务逻辑,time=", time.Now())
			time.Sleep(1 * time.Second)
		}
	}
}

在超时之前cancel

package main

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

func main() {
	// 创建5秒后超时时间的context
	// 5秒之后内部自动执行cancel方法
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)

	// 启动goroutine调用服务
	go callService(ctx)

	time.Sleep(5 * time.Second)

	// 主动调用cancel
	cancel()

	select {
	case <-ctx.Done():
		fmt.Println("收到context的canceled信号,退出主服务,time=", time.Now())
	}
}

// callService 模拟耗时任务
func callService(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("子服务收到context的canceled信号,退出服务,time=", time.Now())
			return
		default:
			fmt.Println("继续执行子服务逻辑,time=", time.Now())
			time.Sleep(1 * time.Second)
		}
	}
}

监听系统信号

package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	// 创建5秒后超时时间的context
	// 5秒之后内部自动执行cancel方法
	ctx, cancel := context.WithCancel(context.Background())

	// 启动goroutine调用服务
	go callService(ctx)

	// 启动goroutine监听系统终止信号
	go func() {
		sigs := make(chan os.Signal)
		signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
		select {
		case <-sigs:
			cancel()
		}
	}()

    // 阻塞,直到context canceled
	select {
	case <-ctx.Done():
		fmt.Println("收到context的canceled信号,退出主服务,time=", time.Now())
	}
}

// callService 模拟耗时任务
func callService(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("子服务收到context的canceled信号,退出服务,time=", time.Now())
			return
		default:
			fmt.Println("继续执行子服务逻辑,time=", time.Now())
			time.Sleep(1 * time.Second)
		}
	}
}

http客户端终止请求

package main

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

func hello(w http.ResponseWriter, req *http.Request) {
	ctx := req.Context()
	fmt.Println("server: hello handler started")
	defer fmt.Println("server: hello handler ended")

	// 阻塞。直到任何一个case满足结束阻塞
	select {
	case <-time.After(10 * time.Second):
		// 10秒之后满足
		fmt.Fprintf(w, "hello\n")
	case <-ctx.Done():
		// http客户端终止请求
		// curl命令调用接口时,按control+c终止API请求
		err := ctx.Err()
		fmt.Println("server:", err)
		internalError := http.StatusInternalServerError
		http.Error(w, err.Error(), internalError)
	}
}

func main() {
	http.HandleFunc("/hello", hello)
	http.ListenAndServe(":8090", nil)
}

参考链接