Context 通常被译作 上下文 ,一般理解为程序单元的一个运行状态、现场、快照,而翻译中 上下 又很好地诠释了其本质,上下上下则是存在上下层的传递,  会把内容传递给  。

在Go语言中,程序单元也就指的是Goroutine。每个Goroutine在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个Context 变量中,传递给要执行的Goroutine中。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。

context 包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,使我们在被调用程序单元的外部,通过设置ctx变量值,将过期或撤销这些信号传递给被调用的程序单元。在网络编程中,若存在A调用B的API, B再调用C的API,若A调用B取消,那也要取消B调用C,通过在A,B,C的API调用之间传递 Context ,以及判断其状态,就能解决此问题,这是为什么gRPC的接口中带上 ctx context.Context 参数的原因之一。

context 包的核心就是 Context 接口,其定义如下:

type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
  • Deadline 会返回一个超时时间,Goroutine获得了超时时间后,例如可以对某些io操作设定超时时间。

  • Done 方法返回一个信道(channel),当 Context 被撤销或过期时,该信道是关闭的,即它是一个表示Context是否已关闭的信号。

  • 当 Done 信道关闭后, Err 方法表明 Contex t被撤的原因。

  • Value 可以让Goroutine共享一些数据,当然获得数据是协程安全的。但使用这些数据的时候要注意同步,比如返回了一个map,而这个map的读写则要加锁。

Context 接口没有提供方法来设置其值和过期时间,也没有提供方法直接将其自身撤销。也就是说, Context 不能改变和撤销其自身。那么该怎么通过 Context 传递改变后的状态呢?

context使用

无论是Goroutine,他们的创建和调用关系总是像层层调用进行的,就像人的辈分一样,而更靠顶部的Goroutine应有办法主动关闭其下属的Goroutine的执行(不然程序可能就失控了)。为了实现这种关系,Context结构也应该像一棵树,叶子节点须总是由根节点衍生出来的。

要创建Context树,第一步就是要得到根节点, context.Background 函数的返回值就是根节点:

func Background() Context

该函数返回空的Context,该Context一般由接收请求的第一个Goroutine创建,是与进入请求对应的Context根节点,它不能被取消、没有值、也没有过期时间。它常常作为处理Request的顶层context存在。

有了根节点,又该怎么创建其它的子节点,孙节点呢?context包为我们提供了多个函数来创建他们:

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

函数都接收一个 Context 类型的参数 parent ,并返回一个 Context 类型的值,这样就层层创建出不同的节点。子节点是从复制父节点得到的,并且根据接收参数设定子节点的一些状态值,接着就可以将子节点传递给下层的Goroutine了。

再回到之前的问题:该怎么通过 Context 传递改变后的状态呢?使用 Context 的Goroutine无法取消某个操作,其实这也是符合常理的,因为这些Goroutine是被某个父Goroutine创建的,而理应只有父Goroutine可以取消操作。在父Goroutine中可以通过WithCancel方法获得一个cancel方法,从而获得cancel的权利。

第一个 WithCancel 函数,它是将父节点复制到子节点,并且还返回一个额外的 CancelFunc 函数类型变量,该函数类型的定义为:

type CancelFunc func()

调用 CancelFunc 对象将撤销对应的 Context 对象,这就是主动撤销 Context 的方法。在父节点的 Context 所对应的环境中,通过 WithCancel 函数不仅可创建子节点的 Context ,同时也获得了该节点 Context 的控制权,一旦执行该函数,则该节点 Context 就结束了,则子节点需要类似如下代码来判断是否已结束,并退出该Goroutine:

select {    case <-cxt.Done():
// do some clean...
}

WithDeadline 函数的作用也差不多,它返回的Context类型值同样是 parent 的副本,但其过期时间由 deadline 和 parent 的过期时间共同决定。当 parent 的过期时间早于传入的 deadline 时间时,返回的过期时间应与 parent 相同。父节点过期时,其所有的子孙节点必须同时关闭;反之,返回的父节点的过期时间则为 deadline 。

WithTimeout 函数与 WithDeadline 类似,只不过它传入的是从现在开始Context剩余的生命时长。他们都同样也都返回了所创建的子Context的控制权,一个 CancelFunc 类型的函数变量。

当顶层的Request请求函数结束后,我们就可以cancel掉某个context,从而层层Goroutine根据判断 cxt.Done() 来结束。

WithValue 函数,它返回 parent 的一个副本,调用该副本的Value(key)方法将得到val。这样我们不光将根节点原有的值保留了,还在子孙节点中加入了新的值,注意若存在Key相同,则会被覆盖。

范例:

package main

import (
"fmt"
"time"
"golang.org/x/net/context"
) func main() {
// ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(time.Second*5))
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*5)
ctx = context.WithValue(ctx, "Test", "123456")
// defer cancelFunc() if t, ok := ctx.Deadline(); ok {
fmt.Println(time.Now())
fmt.Println(t.String())
}
go func(ctx context.Context) {
fmt.Println(ctx.Value("Test"))
for {
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
return
// default:
// continue
}
}
}(ctx)
// if ctx.Err() == nil {
// fmt.Println("Sleep 10 seconds...")
// time.Sleep(time.Second * 10)
// }
// if ctx.Err() != nil {
// fmt.Println("Alredy exit...")
// }
time.Sleep(time.Second * 3)
cancelFunc()
// for {
// if ctx.Err() != nil {
// fmt.Println("gracefully exit...")
// break
// }
// }
}

参考:http://lanlingzi.cn/post/technical/2016/0802_go_context/?utm_source=tuicool&utm_medium=referral

http://blog.csdn.net/u014029783/article/details/53782864

http://blog.csdn.net/zdyueguanyun/article/details/64904703

Golang context包解读的更多相关文章

  1. Golang Context 包详解

    Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也 ...

  2. golang context包

    go context标准库 context包在Go1.7版本时加入到标准库中.其设计目标是给Golang提供一个标准接口来给其他任务发送取消信号和传递数据.其具体作用为: 可以通过context发送取 ...

  3. 【GoLang】golang context channel 详解

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

  4. Golang Context 的原理与实战

    本文让我们一起来学习 golang Context 的使用和标准库中的Context的实现. golang context 包 一开始只是 Google 内部使用的一个 Golang 包,在 Gola ...

  5. golang中的context包

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

  6. golang中context包学习

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

  7. Golang Context 详细介绍

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

  8. golang context 剖析 1.7.4 版本

    1. 内部结构之 - timerCtx . type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. dead ...

  9. 简析 Golang IO 包

    简析 Golang IO 包 io 包提供了 I/O 原语(primitives)的基本接口.io 包中定义了四个最基本接口 Reader.Writer.Closer.Seeker 用于表示二进制流的 ...

随机推荐

  1. java编写带头结点的单链表

    最近在牛客网上练习在线编程,希望自己坚持下去,每天都坚持下去练习,给自己一个沉淀,不多说了 我遇到了一个用java实现单链表的题目,就自己在做题中将单链表完善了一下,希望大家作为参考也熟悉一下,自己 ...

  2. ECLIPSE控制台信息导出

    JAVA代码: String fileAddress = Config.get_storeServiceLogsAsTextFile(); //获取信息的存放位置 //将控制台的信息保存到特定的文件中 ...

  3. POJ3414(KB1-H BFS)

    Pots Description You are given two pots, having the volume of A and B liters respectively. The follo ...

  4. 使用vue+webpack打包时,去掉资源前缀

    在build文件夹下找到webpack.prod.conf.js文件,搜索 filename: utils.assetsPath('css/[name].[contenthash].css'), 将[ ...

  5. centos文件基本操作

    centos彻底删除文件夹.文件命令(centos 新建.删除.移动.复制等命令: 1.新建文件夹 mkdir 文件名 新建一个名为test的文件夹在home下 view source1 mkdir ...

  6. Docker for Windows(一)下载与安装

    一.下载Docker for Windows 下载地址:Docker for Windows 下载完是一个安装程序,双击运行即可.注:如果您的系统不符合运行Docker for Windows的要求, ...

  7. 风云2号云图Mosaic Dataset处理

    # ---------------------------------------------------------------------------# -*- coding: utf-8 -*- ...

  8. private 与 super

    public class Person { private String name; private int age; } public class Student extends Person { ...

  9. mocha、chai、sinon和istanbul实现100%单元测试覆盖率

    敏捷软件开发中,最重要实践的就是测试驱动开发,在单元测试层面,我们试着实现一个重要的指标就是测试覆盖率.测试覆盖率衡量我们的代码是否已经全部被测试到了. 但是指标本身不是目的,借助测试覆盖率检查,我们 ...

  10. Selenium clear()方法无法清掉数据

    问题描述 clear()方法执行过后, 数据还是在. 根本原因 存在镜像节点. 操作clear()清掉数据后, 镜像节点的数据还在, 就会再补充回去. 解决办法 添加下面代码就可以连同镜像的数据一起去 ...