• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

golang context ctx

武飞扬头像
贝拉今天吃好的
帮助1

Go ontext Note1

在最近的面试过程被问到context(上下文,ctx)相关的内容,由于理解不是很清晰导致自己被问住了,故做一个笔记类型的梳理。另外声明:以下内容包括个人理解,如有错误欢迎指正^_^

context参考连接:go官方context库文件,go_blogB站视频1

介绍

context的出现是为方便goroutines之间传递请求信息,如请求到达需要request handler,而连接DB需要另一个goroutine以及可能存在的rpc goroutine。使用context能够携带请求的一些信息,如超时信息等。

context.go 源码

在go标准库的context接口包括以下内容

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key any) any
}
方法 简要描述
Deadline 该方法返回deadline截止日期,未设置deadlineok==false
Done 只读管道,用于实现阻塞
Err 用于记录ctx的错误原因
Value 用来返回key对应的value,相当于ctx内部的map

deadline被调用时会关闭管道,并将为什么关闭记录在Err里 注意:对于value的官方建议用法:Use context values only for request-scoped data that transits processes and API boundaries, not for passing optional parameters to functions.

参考下图中多个功能执行过程,以context作为函数之间的消息传递。 学新通 图源自BiliBili

而在context初始化方面,需要借助空的context,在context包中有两种方法获取空的context,如下:

var (
	background = new(emptyCtx)//不可导出
	todo       = new(emptyCtx)
)
func Background() Context {//可导出
	return background
}
func TODO() Context {
	return todo
}

实际上,Background()和TODO()都是构建一个空的context,只是语义上略有区别(更详细的可以看context源码,其中的注释说明B方法更适合于main中,而TODO则偏向于测试环境?个人理解)

常用方法

在context中有以下常用方法,包括context.WithValue,context.WithCancel,context.WithTimeout

example

context.WithValue

func main() {
	grandp := context.Background()//empty context
	father := tct1(grandp)//调用context.WithValue进行赋值,相当于继承自grandp
	fmt.Printf("key1 %s", father.Value("key1"))
}
func tct1(ctx context.Context) context.Context {
    //以hischild将接受context并给value赋值
	hischild := context.WithValue(ctx, "key1", "value1")
	return hischild
}
//输出 :key1 value1

context.WithCancel

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		time.Sleep(100 * time.Millisecond)
		cancel() //调用cancel,ctx.Done()通道关闭
	}()
	select {
	case <-time.After(300 * time.Millisecond): 
		fmt.Println("未超时")
	case <-ctx.Done(): //ctx.Done()是一个管道,调用了cancel()都会关闭这个管道,然后读操作就会立即返回
		err := ctx.Err()        //如果发生Done(管道被关闭),Err返回Done的原因,可能是被Cancel了,也可能是超时了
		fmt.Println("超时:", err) //context canceled
	}
}
//输出:超时: context canceled

由于在100ms处就执行了cancel(),所以会率先选择ctx.Done通道,而time.After返回的是一个<-chan Time只读管道,如果Sleep的时间长于300则会“未超时”。

context.WithTimeout

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) //超时后会自动调用context的Deadline,Deadline会,触发Done
	defer cancel()
	select {
	case <-time.After(300 * time.Millisecond):
		fmt.Println("未超时")
	case <-ctx.Done(): //ctx.Done()是一个管道,context超时或者调用了cancel()都会关闭这个管道,然后读操作就会立即返回
		err := ctx.Err()          //如果发生Done(管道被关闭),Err返回Done的原因,可能是被Cancel了,也可能是超时了
		fmt.Println("超时:", err) //context deadline exceeded
	}
}
 //输出:超时: context deadline exceeded

实际上,context.WithTimeout只是在WithCancel上指定超时时间,这一应用在Http请求处理等耗时处理中应用广泛。

Timeout的继承问题

通过context.WithTimeout创建的Context,其寿命不会超过父Context的寿命。比如: A处ctxA还有10s过期,而2s后B处接受该ctxA并使用WithTime设为100s后过期,而实际上8s后ctxB也会到期。

在Http中的应用

func hello(w http.ResponseWriter, req *http.Request) {
    ctx := req.Context()
    fmt.Println("server: hello handler started")
    defer fmt.Println("server: hello handler ended")
//net/http为每个请求创建了一个 context.Context, 并且可以通过 Context() 方法获取并使用它。
    select {
    case <-time.After(10 * time.Second)://模拟计算耗时
        fmt.Fprintf(w, "hello\n")
    case <-ctx.Done():
        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)
}

在两个终端启动:

go run context-in-http-servers.go #//TerminalA
curl 127.0.0.1:8090/hello   #//TerminalB
------------------------------------------------
#输出:
server: hello handler started 
#Ctrl c终止TerminalB,10s前!
server: context canceled
server: hello handler ended
#不中断TerminalB中curl
server: hello handler started
server: hello handler ended
#正常开始以及结束

这里在TerminalB中,模拟客户端发出 /hello 请求, 在服务端开始处理后,按下 Ctrl C 以发出取消信号,从而导致关闭通道输出context的err。


2023年8月6日20:30:47

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgajbhh
系列文章
更多 icon
同类精品
更多 icon
继续加载