golang Context应用举例
Context本质
golang标准库里Context实际上是一个接口(即一种编程规范、 一种约定)。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
通过查看源码里的注释,我们得到如下约定:
- Done()函数返回一个只读管道,且管道里不存放任何元素(struct{}),所以用这个管道就是为了实现阻塞
- Deadline()用来记录到期时间,以及是否到期。
- Err()用来记录Done()管道关闭的原因,比如可能是因为超时,也可能是因为被强行Cancel了。
- Value()用来返回key对应的value,你可以想像成Context内部维护了一个map。
Context实现
go源码里提供了Context接口的一个具体实现,遗憾的是它只是一个空的Context,什么也没做。
type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
} func (*emptyCtx) Done() <-chan struct{} {
return nil
} func (*emptyCtx) Err() error {
return nil
} func (*emptyCtx) Value(key any) any {
return nil
}
emptyCtx以小写开头,包外不可见,所以golang又提供了Background和TODO这2个函数让我们能获取到emptyCtx。
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
backgroud和todo明明是一模一样的东西,就是emptyCtx,为什么要搞2个呢?真心求教,知道的同学请在评论区告诉我。
emptyCtx有什么用?创建Context时通常需要传递一个父Context,emptyCtx用来充当最初的那个Root Context。
With Value
当业务逻辑比较复杂,函数调用链很长时,参数传递会很复杂,如下图:
f1产生的参数b要传给f2,虽然f2并不需要参数b,但f3需要,所以b还是得往后传。
如果把每一步产生的新变量都放到Context这个大容器里,函数之间只传递Context,需要什么变量时直接从Context里取,如下图:
f2能从context里取到a和b,f4能从context里取到a、b、c、d。
package main import (
"context"
"fmt"
) func step1(ctx context.Context) context.Context {
//根据父context创建子context,创建context时允许设置一个<key,value>对,key和value可以是任意数据类型
child := context.WithValue(ctx, "name", "大脸猫")
return child
} func step2(ctx context.Context) context.Context {
fmt.Printf("name %s\n", ctx.Value("name"))
//子context继承了父context里的所有key value
child := context.WithValue(ctx, "age", 18)
return child
} func step3(ctx context.Context) {
fmt.Printf("name %s\n", ctx.Value("name")) //取出key对应的value
fmt.Printf("age %d\n", ctx.Value("age"))
} func main1() {
grandpa := context.Background() //空context
father := step1(grandpa) //father里有一对<key,value>
grandson := step2(father) //grandson里有两对<key,value>
step3(grandson)
}
Timeout
在视频 https://www.bilibili.com/video/BV1C14y127sv/ 里介绍了超时实现的核心原理,视频中演示的done管道可以用Context的Done()来替代,Context的Done()管道什么时候会被关系呢?2种情况:
1. 通过context.WithCancel创建一个context,调用cancel()时会关闭context.Done()管道。
func f1() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(100 * time.Millisecond)
cancel() //调用cancel,触发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
}
}
2. 通过context.WithTimeout创建一个context,当超过指定的时间或者调用cancel()时会关闭context.Done()管道。
func f2() {
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
}
}
Timeout的继承问题
通过context.WithTimeout创建的Context,其寿命不会超过父Context的寿命。比如:
- 父Context设置了10号到期,5号诞生了子Context,子Context设置了100天后到期,则实际上10号的时候子Context也会到期。
- 父Context设置了10号到期,5号诞生了子Context,子Context设置了1天后到期,则实际上6号的时候子Context就会到期。
func inherit_timeout() {
parent, cancel1 := context.WithTimeout(context.Background(), time.Millisecond*1000) //parent设置100ms超时
t0 := time.Now()
defer cancel1() time.Sleep(500 * time.Millisecond) //消耗掉500ms // child, cancel2 := context.WithTimeout(parent, time.Millisecond*1000) //parent还剩500ms,child设置了1000ms之后到期,child.Done()管道的关闭时刻以较早的为准,即500ms后到期
child, cancel2 := context.WithTimeout(parent, time.Millisecond*100) //parent还剩500ms,child设置了100ms之后到期,child.Done()管道的关闭时刻以较早的为准,即100ms后到期
t1 := time.Now()
defer cancel2() select {
case <-child.Done():
t2 := time.Now()
fmt.Println(t2.Sub(t0).Milliseconds(), t2.Sub(t1).Milliseconds())
fmt.Println(child.Err()) //context deadline exceeded
}
}
context超时在http请求中的实际应用
定心丸来了,最后说一遍:”context在实践中真的很有用“
客户端发起http请求时设置了一个2秒的超时时间:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"time"
) func main() {
client := http.Client{
Timeout: 2 * time.Second, //小于10秒,导致请求超时,会触发Server端的http.Request.Context的Done
}
if resp, err := client.Get("http://127.0.0.1:5678/"); err == nil {
defer resp.Body.Close()
fmt.Println(resp.StatusCode)
if bs, err := ioutil.ReadAll(resp.Body); err == nil {
fmt.Println(string(bs))
}
} else {
fmt.Println(err) //Get "http://127.0.0.1:5678/": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
}
}
服务端从Request里取提context,故意休息10秒钟,同时监听context.Done()管道有没有关闭。由于Request的context是2秒超时,所以服务端还没休息够context.Done()管道就关闭了。
package main
import (
"fmt"
"net/http"
"time"
) func welcome(w http.ResponseWriter, req *http.Request) {
ctx := req.Context() //取得request的context
select {
case <-time.After(10 * time.Second): //故意慢一点,10秒后才返回结果
fmt.Fprintf(w, "welcome")
case <-ctx.Done(): //超时后client会撤销请求,触发ctx.cancel(),从而关闭Done()管道
err := ctx.Err() //如果发生Done(管道被关闭),Err返回Done的原因,可能是被Cancel了,也可能是超时了
fmt.Println("server:", err) //context canceled
}
} func main() {
http.HandleFunc("/", welcome)
http.ListenAndServe(":5678", nil)
}
golang Context应用举例的更多相关文章
- Golang Context 详细介绍
Golang context 本文包含对context实现上的分析和使用方式,分析部分源码讲解比价多,可能会比较枯燥,读者可以直接跳过去阅读使用部分. ps: 作者本着开源分享的精神撰写本篇文章,如果 ...
- Golang Context 包详解
Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也 ...
- 带小伙伴手写 golang context
前言 - context 源码 可以先了解官方 context.go 轮廓. 这里捎带保存一份当前 context 版本备份. // Copyright 2014 The Go Authors. Al ...
- golang context学习记录1
1.前言 一个请求,可能涉及多个API调用,多个goroutine,如何在多个API 之间,以及多个goroutine之间协作和传递信息,就是一个问题. 比如一个网络请求Request,需要开启一些g ...
- Golang Context 的原理与实战
本文让我们一起来学习 golang Context 的使用和标准库中的Context的实现. golang context 包 一开始只是 Google 内部使用的一个 Golang 包,在 Gola ...
- 【GoLang】golang context channel 详解
代码示例: package main import ( "fmt" "time" "golang.org/x/net/context" ) ...
- Golang context包解读
Context 通常被译作 上下文 ,一般理解为程序单元的一个运行状态.现场.快照,而翻译中 上下 又很好地诠释了其本质,上下上下则是存在上下层的传递, 上 会把内容传递给 下 . 在Go语言中,程序 ...
- golang context 剖析 1.7.4 版本
1. 内部结构之 - timerCtx . type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. dead ...
- golang Context for goroutines
概要 goroutine 的控制 取消控制 超时控制 goroutine 之间的传值 总结 概要 golang 的提供的 channel 机制是基于 CSP(Communicating Sequenc ...
- golang context包
go context标准库 context包在Go1.7版本时加入到标准库中.其设计目标是给Golang提供一个标准接口来给其他任务发送取消信号和传递数据.其具体作用为: 可以通过context发送取 ...
随机推荐
- 让ChatGPT来写今年的高考作文,能得几分?
使用最新的ChatGPT4模型,做2023年全国甲卷的高考作文. 作文考试题目如下 人们因技术发展得以更好地掌控时间,但也有人因此成了时间的仆人.这句话引发了你怎样的联想与思考?请写一篇文章. 要求: ...
- PQ常用模板
//json请求 Json.Document(Web.Contents("",[Headers=[#"cookie"=tk,#"Content-Typ ...
- 一分钟学一个 Linux 命令 - tar
前言 大家好,我是 god23bin.今天给大家带来的是 Linux 命令系列,每天只需一分钟,记住一个 Linux 命令不成问题.今天,我们要介绍的是一个常用且强大的命令:tar. 什么是 tar ...
- Java 实战介绍 Cookie 和 Session 的区别
HTTP 是一种不保存状态的协议,即无状态协议,HTTP 协议不会保存请求和响应之间的通信状态,协议对于发送过的请求和响应都不会做持久化处理. 无状态协议减少了对服务压力,如果一个服务器需要处理百万级 ...
- @FunctionalInterface注解的使用
被@FunctionalInterface注解标记的类型表明这是一个函数接口.从概念上讲,函数接口只有一个抽象方法.如果接口声明的抽象方法覆写Object类的公共方法,那这方法不算作接口的抽象方法,因 ...
- 【QCustomPlot】绘制 x-y 曲线图
说明 使用 QCustomPlot 绘图库辅助开发时整理的学习笔记.同系列文章目录可见 <绘图库 QCustomPlot 学习笔记>目录.本篇介绍如何使用 QCustomPlot 绘制 x ...
- 基于picker封装的移动端简单实用选择器select插件
基于picker封装的移动端简单实用选择器select插件; 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=12797 效果图如 ...
- Python编程和数据科学中的大数据分析:如何从大量数据中提取有意义的信息和模式
目录 <Python编程和数据科学中的大数据分析:如何从大量数据中提取有意义的信息和模式> 引言 大数据时代已经来临,随着互联网和物联网的普及,海量数据的产生和存储已经成为一种普遍的现象. ...
- 图书商城项目练习②后端服务Node/Express/Sqlite
本系列文章是为学习Vue的项目练习笔记,尽量详细记录一下一个完整项目的开发过程.面向初学者,本人也是初学者,搬砖技术还不成熟.项目在技术上前端为主,包含一些后端代码,从基础的数据库(Sqlite).到 ...
- 解决安装报错 mysqlclient-1.4.6-cp38-cp38-win32.whl is not a supported wheel on this platform.
解决方法, 重命名 先查看pip对应匹配的名称 在PyCharm中查看 打开下边栏的Terminal,输入 pip debug --verbose 修改为一致后 最后进行安装 进入该安装包目录下,c ...