转载自: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. Java-IO之CharArrayWriter(字符数组输出流)

    CharArrayWriter用于写数据,数据单位是字符. (1) 通过CharArrayWriter()创建的CharArrayWriter对应的字符数组大小是32. (2) 通过CharArray ...

  2. 编译Android 4.4.2源码

    在之前的文章中,和大家分享了在天朝下下载android 4.4.2源码的过程(详见下载android4.4.2源码全过程(附已下载的源码)),现在写下编译的笔记. 虽然在android doc中,有提 ...

  3. JAVA对象克隆可能会出现的问题

    首先,区分一下拷贝和克隆: 拷贝:当拷贝一个变量时,原始变量与拷贝变量引用的是同一个对象.当改变一个变量所引用的对象,则会对另一个变量造成影响. 克隆:当克隆一个对象时,是重新的创建了和该对象内容相同 ...

  4. Visual Studio 2010多线程编程

    随着处理数据量的逐渐增大,串行单核的程序,犹如残灯缺月,无法满足运用需求.大规模集群的出现,解决了这一技术难题.本文旨在探讨如何使用多CPU并行编程,关于CUDA的并行前面文章已有讲述.本文结构分为三 ...

  5. JSP编译成Servlet(五)JDT Compiler编译器

    通过JSP编译器编译后生成了对应的java文件,接下去要把Java文件编译成class文件.对于这部分完全没有必要重新造轮子,常见的优秀编译工具有Eclipse JDT Java编译器和Ant编译器. ...

  6. C 语言之银行ATM机界面

    其实就是简单地对switch的用法,希望能给广大读者一些思路,写出自己的创意界面. #include <stdio.h> void main() { char SelectKey,Cred ...

  7. MinerConstanits.java 常量类

    MinerConstanits.java 常量类 package com.iteye.injavawetrust.miner; /** * 常量类 * @author InJavaWeTrust * ...

  8. C++ Primer 有感(类)

    1.在类内部,声明成员函数时必需 的,而定义成员函数则是可选的.在类内部定义的函数默认为inline. 2.const成员函数不能改变其所操作的对象的数据成员.const必须同时出现在声明和定义中,若 ...

  9. (二十八)QQ好友列表的展开收缩

    要通过监听HeaderView上面的Button来进行操作: 通过addTarget方法即可,应该将按钮的点击方法封装在HearView控制器内部. 列表收起来的原理: tableView: numb ...

  10. Linux管道编程实例

    /*管道 可以把管道想象为两个实体之间的单向连接器.注意,管道是半双工的, 如果需要全双工通讯,应该转而考虑套接字. 匿名管道又称管道,提供了一个进程与它的兄弟进程通讯的方法,只存在于父进程中: 命名 ...