Golang之Context的使用

时间:2022-04-17 09:59:34

转载自:http://www.nljb.net/default/Golang%E4%B9%8BContext%E7%9A%84%E4%BD%BF%E7%94%A8/

简介

在golang中的创建一个新的线程并不会返回像c语言类似的pid
所有我们不能从外部杀死某个线程,所有我就得让它自己结束
之前我们用channel+select的方式,来解决这个问题
但是有些场景实现起来比较麻烦,例如由一个请求衍生出多个线程
并且之间需要满足一定的约束关系,以实现一些诸如:
有效期,中止线程树,传递请求全局变量之类的功能。
于是google 就为我们提供一个解决方案,开源了context包。
使用context包来实现上下文功能 .....
约定:需要在你的方法的传入参数的第一个参数是context.Context的变量。

其实本身非常简单,在导入这个包之后,初始化Context对象,在每个资源访问方法中都调用它,然后在使用时检查Context对象是否已经被Cancel,如果是就释放绑定的资源


源码剖析

context.Context 接口

  • context包里的方法是线程安全的,可以被多个线程使用
  • 当Context被canceled或是timeout, Done返回一个被closed 的channel
  • 在Done的channel被closed后, Err代表被关闭的原因
  • 如果存在, Deadline 返回Context将要关闭的时间
  • 如果存在,Value 返回与 key 相关了的值,不存在返回 nil
// context 包的核心
type Context interface {
Done() <-chan struct{}
Err() error
Deadline() (deadline time.Time, ok bool)
Value(key interface{}) interface{}
}

我们不需要手动实现这个接口,context 包已经给我们提供了两个

一个是 Background(),一个是 TODO()
这两个函数都会返回一个Context的实例
只是返回的这两个实例都是空 Context。
/*
TODO返回一个非空,空的上下文
在目前还不清楚要使用的上下文或尚不可用时
*/
context.TODO()
/*
Background返回一个非空,空的上下文。
这是没有取消,没有值,并且没有期限。
它通常用于由主功能,初始化和测试,并作为输入的顶层上下文
*/
context.Background()

主要方法

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key interface{}, val interface{}) Context

WithCancel 对应的是 cancelCtx ,其中,返回一个 cancelCtx ,同时返回一个 CancelFunc,CancelFunc 是 context 包中定义的一个函数类型:type CancelFunc func()。调用这个 CancelFunc 时,关闭对应的c.done,也就是让他的后代goroutine退出

WithDeadline 和 WithTimeout 对应的是 timerCtx ,WithDeadline 和 WithTimeout 是相似的,WithDeadline 是设置具体的 deadline 时间,到达 deadline 的时候,后代 goroutine 退出,而 WithTimeout 简单粗暴,直接 return WithDeadline(parent, time.Now().Add(timeout))

WithValue 对应 valueCtx ,WithValue 是在 Context 中设置一个 map,拿到这个 Context 以及它的后代的 goroutine 都可以拿到 map 里的值

context的创建

所有的context的父对象,也叫根对象,是一个空的context,它不能被取消,它没有值,从不会被取消,也没有超时时间,它常常作为处理request的顶层context存在,然后通过WithCancel、WithTimeout函数来创建子对象来获得cancel、timeout的能力

当顶层的request请求函数结束后,我们就可以cancel掉某个context,从而通知别的routine结束

WithValue方法可以把键值对加入context中,让不同的routine获取

官方案例

// 在 handle 环境中使用
func handleSearch(w http.ResponseWriter, req *http.Request) {
// ctx is the Context for this handler. Calling cancel closes the
// ctx.Done channel, which is the cancellation signal for requests
// started by this handler.
var (
ctx context.Context
cancel context.CancelFunc
)
// 获取参数 ...
timeout, err := time.ParseDuration(req.FormValue("timeout"))
if err == nil {
// The request has a timeout, so create a context that is
// canceled automatically when the timeout expires.
// 获取成功, 则按照参数设置超时时间
ctx, cancel = context.WithTimeout(context.Background(), timeout)
} else {
// 获取失败, 则在该函数结束时结束 ...
ctx, cancel = context.WithCancel(context.Background())
}
// ----------------
// 这样随着cancel的执行,所有的线程都随之结束了 ...
go A(ctx) +1
go B(ctx) +2
go C(ctx) +3
// ----------------
defer cancel() // Cancel ctx as soon as handleSearch returns.
} // 监听 ctx.Done() 结束 ...
func A(ctx context.Context) int {
// ... TODO
select {
case <-ctx.Done():
return -1
default:
// 没有结束 ... 执行 ...
}
}

ctxhttp.go

package ctxhttp // import "golang.org/x/net/context/ctxhttp"

import (
"io"
"net/http"
"net/url"
"strings" "golang.org/x/net/context"
) // Do sends an HTTP request with the provided http.Client and returns
// an HTTP response.
//
// If the client is nil, http.DefaultClient is used.
//
// The provided ctx must be non-nil. If it is canceled or times out,
// ctx.Err() will be returned.
func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
if client == nil {
client = http.DefaultClient
}
resp, err := client.Do(req.WithContext(ctx))
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
if err != nil {
select {
case <-ctx.Done():
err = ctx.Err()
default:
}
}
return resp, err
} // Get issues a GET request via the Do function.
func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return Do(ctx, client, req)
} // Head issues a HEAD request via the Do function.
func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return nil, err
}
return Do(ctx, client, req)
} // Post issues a POST request via the Do function.
func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", bodyType)
return Do(ctx, client, req)
} // PostForm issues a POST request via the Do function.
func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}

使用示例

package main

import (
"fmt"
"time" "golang.org/x/net/context"
) func Cdd(ctx context.Context) int {
fmt.Println(ctx.Value("NLJB"))
select {
// 结束时候做点什么 ...
case <-ctx.Done():
return -3
default:
// 没有结束 ... 执行 ...
}
} func Bdd(ctx context.Context) int {
fmt.Println(ctx.Value("HELLO"))
fmt.Println(ctx.Value("WROLD"))
ctx = context.WithValue(ctx, "NLJB", "NULIJIABEI")
go fmt.Println(Cdd(ctx))
select {
// 结束时候做点什么 ...
case <-ctx.Done():
return -2
default:
// 没有结束 ... 执行 ...
}
} func Add(ctx context.Context) int {
ctx = context.WithValue(ctx, "HELLO", "WROLD")
ctx = context.WithValue(ctx, "WROLD", "HELLO")
go fmt.Println(Bdd(ctx))
select {
// 结束时候做点什么 ...
case <-ctx.Done():
return -1
default:
// 没有结束 ... 执行 ...
}
} func main() { // 自动取消(定时取消)
{
timeout := 3 * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
fmt.Println(Add(ctx))
}
// 手动取消
// {
// ctx, cancel := context.WithCancel(context.Background())
// go func() {
// time.Sleep(2 * time.Second)
// cancel() // 在调用处主动取消
// }()
// fmt.Println(Add(ctx))
// }
select {} }
package main

import (
"fmt"
"time"
"golang.org/x/net/context"
) // 模拟一个最小执行时间的阻塞函数
func inc(a int) int {
res := a + 1 // 虽然我只做了一次简单的 +1 的运算,
time.Sleep(1 * time.Second) // 但是由于我的机器指令集中没有这条指令,
// 所以在我执行了 1000000000 条机器指令, 续了 1s 之后, 我才终于得到结果。B)
return res
} // 向外部提供的阻塞接口
// 计算 a + b, 注意 a, b 均不能为负
// 如果计算被中断, 则返回 -1
func Add(ctx context.Context, a, b int) int {
res := 0
for i := 0; i < a; i++ {
res = inc(res)
select {
case <-ctx.Done():
return -1
default:
// 没有结束 ... 执行 ...
}
}
for i := 0; i < b; i++ {
res = inc(res)
select {
case <-ctx.Done():
return -1
default:
// 没有结束 ... 执行 ...
}
}
return res
} func main() {
{
// 使用开放的 API 计算 a+b
a := 1
b := 2
timeout := 2 * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
res := Add(ctx, 1, 2)
fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
}
{
// 手动取消
a := 1
b := 2
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel() // 在调用处主动取消
}()
res := Add(ctx, 1, 2)
fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
}
}

Golang之Context的使用的更多相关文章

  1. golang中Context的使用场景

    golang中Context的使用场景 context在Go1.7之后就进入标准库中了.它主要的用处如果用一句话来说,是在于控制goroutine的生命周期.当一个计算任务被goroutine承接了之 ...

  2. Golang的Context介绍及其源码分析

    简介 在Go服务中,对于每个请求,都会起一个协程去处理.在处理协程中,也会起很多协程去访问资源,比如数据库,比如RPC,这些协程还需要访问请求维度的一些信息比如说请求方的身份,授权信息等等.当一个请求 ...

  3. 深入理解golang:Context

    一.背景 在golang中,最主要的一个概念就是并发协程 goroutine,它只需用一个关键字 go 就可以开起一个协程,并运行. 一个单独的 goroutine运行,倒也没什么问题.如果是一个go ...

  4. golang 之 context包

    概述     context是Go中广泛使用的程序包,由Google官方开发,在1.7版本引入.它用来简化在多个go routine传递上下文数据.(手动/超时)中止routine树等操作,比如,官方 ...

  5. golang中context包学习

    摘要 go语言中goroutine之间的关联关系,缺乏维护,在erlang中有专门的机制来保障新开仟程的生命周期, 在go语言中,只能通过channel + select来实现,但不够直观,感觉很绕. ...

  6. golang从context源码领悟接口的设计

    注:写帖子时go的版本是1.12.7 go语言中实现一个interface不用像其他语言一样需要显示的声明实现接口.go语言只要实现了某interface的方法就可以做类型转换.go语言没有继承的概念 ...

  7. 【GoLang】golang context channel 详解

    代码示例: package main import ( "fmt" "time" "golang.org/x/net/context" ) ...

  8. golang中的context包

    标准库的context包 从设计角度上来讲, golang的context包提供了一种父routine对子routine的管理功能. 我的这种理解虽然和网上各种文章中讲的不太一样, 但我认为基本上还是 ...

  9. Golang Context 详细介绍

    Golang context 本文包含对context实现上的分析和使用方式,分析部分源码讲解比价多,可能会比较枯燥,读者可以直接跳过去阅读使用部分. ps: 作者本着开源分享的精神撰写本篇文章,如果 ...

随机推荐

  1. Hashtable和HashMap类的区别

    Hashtable和HashMap类有三个重要的不同之处.第一个不同主要是历史原因.Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现. ...

  2. UITableview reloadData Animation 动画效果

    http://blog.kingiol.com/blog/2013/10/22/uitableview-reloaddata-with-animation/ 运用到UITableview进行重新加载数 ...

  3. 通过DDOS攻击流程图来浅谈如何预防Ddos攻击与防御

    DDOS攻击流程图 站长之家配图(来源:ppkj.net) 一 背景 在前几天,我们运营的某网站遭受了一次ddos攻击,我们的网站是一个公益性质的网站,为各个厂商和白帽子之间搭建一个平台以传递安全问题 ...

  4. 初识python yield

    for sel in response.xpath('//ul/li'): item = DmozItem() item['title'] = sel.xpath('a/text()').extrac ...

  5. 湖南省第八届大学生程序设计大赛原题 D - 平方根大搜索 UVA 12505 - Searching in sqrt&lpar;n&rpar;

    http://acm.hust.edu.cn/vjudge/contest/view.action?cid=30746#problem/D D - 平方根大搜索 UVA12505 - Searchin ...

  6. mysql常见的优化方法

    1.选取适当的字段属性.例如,在定义邮政编码这个字段时,如果将其设置为CHAR(255),显然给数据库增加了不必要的空间,甚至使用VARCHAR这种类型也是多余的,因为CHAR(6)就可以很好的完成任 ...

  7. 16&period;观察者模式(Observer Pattern)

    动机(Motivate):     在软件构建 过程中,我们需要为某些对象建立一种“通知依赖关系” --------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知.如果这 ...

  8. TX2-入门坑点-Ubuntu16&period;04任务栏消失

    问题:安装cuda过程中出现任务栏消失,快捷键失效的现象. 解决方法:重装桌面无法解决,使用cssm无法解决,最后删除home目录下的 .cache文件夹重启后解决

  9. 一个简单CI&sol;CD流程的思考

    因为公司有两地研发团队,在统一CI/CD上难度不亚于两家公司合并后的新流程建立,并非不可攻克,简单描述下心得. 首先,代码管理使用gerrit -> 因其强大的 codereview 功能被选中 ...

  10. linux下安装openoffice

    一.环境 centos6.9 安装jdk1.6及以上 二.安装依赖 yum install libXext.x86_64 -y yum install freetype -y yum groupins ...