转载自: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. 自定义android 4.0以上的对话框风格

    做个笔记,这里是Dialog的风格,如果是用AlertDialog创建的,不能直接用.在styles.xml的写法: <style name="DialogWindowTitle&qu ...

  2. 手把手带你走进MVP +Dagger2 + DataBinding+ Rxjava+Retrofit 的世界

    0.0 Android开发现在的变化用一个词来形容就是 :翻天覆地 越来越多的项目使用了MVP + Rxjava+Retrofit +Dagger2 + DataBinding等等东西.. 但是这些东 ...

  3. Android官方命令深入分析之etc1tool

    etc1tool是一个命令行工具,可以将PNG图像压缩为etc1标准,并且可以进行解压缩. 用法: etc1tool infile [--help | --encode | --encodeNoHea ...

  4. 我眼中的Linux设备树(五 根节点)

    五 根节点一个最简单的设备树必须包含根节点,cpus节点,memory节点.根节点的名字及全路径都是"/",至少需要包含model和compatible两个属性.model属性我们 ...

  5. Linux0.11进程分配时间片的策略

    想知道内核什么时候给进程重新分配时间片,最好的办法就是阅读源代码(其中已经打了注释) /******************************************************** ...

  6. jenkin插件整理

    分类 plugin名称 wiki地址 源码地址 plugin作用范围 备注 Build Reports构建报告(此类插件用来分析构建结果,比果代码检查,测试CASE分析,并将这些结果以报表,趋势图等形 ...

  7. Css详解之(选择器)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. Linux0.11进程切换和TSS结构

    TSS 全称为task state segment,是指在操作系统进程管理的过程中,进程切换时的任务现场信息.       X86体系从硬件上支持任务间的切换.为此目的,它增设了一个新段:任务状态段( ...

  9. wing带你玩转自定义view系列(1) 仿360内存清理效果

    本篇是接自 手把手带你做自定义view系列 宗旨都是一样,带大家一起来研究自定义view的实现,与其不同的是本系列省去了简单的坐标之类的讲解,重点在实现思路,用简洁明了的文章,来与大家一同一步步学习. ...

  10. android 面试之listview

    ListView优化一直是一个老生常谈的问题,不管是面试还是平常的开发中,ListView永远不会被忽略掉,那么这篇文章我们来看看如何最大化的优化ListView的性能.· 1.在adapter中的g ...