Golang并发模型之Context详解-阿里云开发者社区

开发者社区> sunsky303> 正文

Golang并发模型之Context详解

简介:   对于 Golang 开发者来说context(上下文)包一定不会陌生。但很多时候,我们懒惰的只是见过它,或能起到什么作用,并不会去深究它。 应用场景:在 Go http 包的 Server 中,每一个请求在都有一个对应的goroutine去处理。
+关注继续查看

 

对于 Golang 开发者来说context(上下文)包一定不会陌生。但很多时候,我们懒惰的只是见过它,或能起到什么作用,并不会去深究它。

应用场景:在 Go http 包的 Server 中,每一个请求在都有一个对应的goroutine去处理。请求处理函数通常会启动额外的goroutine用来访问后端服务,比如数据库和 RPC 服务。用来处理一个请求的goroutine通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的 token、请求的截止时间。当一个请求被取消或超时时,所有用来处理该请求的goroutine都应该迅速退出,然后系统才能释放这些goroutine占用的资源,官方博客

注意:go1.6及之前版本请使用golang.org/x/net/contextgo1.7及之后已移到标准库context

Context 原理

Context 的调用应该是链式的,通过WithCancelWithDeadlineWithTimeoutWithValue派生出新的 Context。当父 Context 被取消时,其派生的所有 Context 都将取消。

通过context.WithXXX都将返回新的 Context 和 CancelFunc。调用 CancelFunc 将取消子代,移除父代对子代的引用,并且停止所有定时器。未能调用 CancelFunc 将泄漏子代,直到父代被取消或定时器触发。go vet工具检查所有流程控制路径上使用 CancelFuncs。

遵循规则

遵循以下规则,以保持包之间的接口一致,并启用静态分析工具以检查上下文传播。

  1. 不要将 Contexts 放入结构体,相反context应该作为第一个参数传入,命名为ctx。 func DoSomething(ctx context.Context,arg Arg)error { // ... use ctx ... }
  2. 即使函数允许,也不要传入nil的 Context。如果不知道用哪种 Context,可以使用context.TODO()
  3. 使用context的Value相关方法只应该用于在程序和接口中传递的和请求相关的元数据,不要用它来传递一些可选的参数
  4. 相同的 Context 可以传递给在不同的goroutine;Context 是并发安全的。

Context 包

Context 结构体。

// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
    // Done returns a channel that is closed when this Context is canceled
    // or times out.
    Done() <-chan struct{}

    // Err indicates why this context was canceled, after the Done channel
    // is closed.
    Err() error

    // Deadline returns the time when this Context will be canceled, if any.
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none.
    Value(key interface{}) interface{}
}
  • Done(),返回一个channel。当times out或者调用cancel方法时,将会close掉。
  • Err(),返回一个错误。该context为什么被取消掉。
  • Deadline(),返回截止时间和ok。
  • Value(),返回值。

所有方法

func Background() Context
func TODO() 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, val interface{}) Context

上面可以看到Context是一个接口,想要使用就得实现其方法。在context包内部已经为我们实现好了两个空的Context,可以通过调用Background()和TODO()方法获取。一般的将它们作为Context的根,往下派生。

WithCancel 例子

WithCancel 以一个新的 Done channel 返回一个父 Context 的拷贝。

   229  func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
   230      c := newCancelCtx(parent)
   231      propagateCancel(parent, &c)
   232      return &c, func() { c.cancel(true, Canceled) }
   233  }
   234  
   235  // newCancelCtx returns an initialized cancelCtx.
   236  func newCancelCtx(parent Context) cancelCtx {
   237      return cancelCtx{
   238          Context: parent,
   239          done:    make(chan struct{}),
   240      }
   241  }

此示例演示使用一个可取消的上下文,以防止 goroutine 泄漏。示例函数结束时,defer 调用 cancel 方法,gen goroutine 将返回而不泄漏。

package main

import (
    "context"
    "fmt"
)

func main() {
    // gen generates integers in a separate goroutine and
    // sends them to the returned channel.
    // The callers of gen need to cancel the context once
    // they are done consuming generated integers not to leak
    // the internal goroutine started by gen.
    gen := func(ctx context.Context) <-chan int {
        dst := make(chan int)
        n := 1
        go func() {
            for {
                select {
                case <-ctx.Done():
                    return // returning not to leak the goroutine
                case dst <- n:
                    n++
                }
            }
        }()
        return dst
    }

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // cancel when we are finished consuming integers

    for n := range gen(ctx) {
        fmt.Println(n)
        if n == 5 {
            break
        }
    }
}

WithDeadline 例子

   369  func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
   370      if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
   371          // The current deadline is already sooner than the new one.
   372          return WithCancel(parent)
   373      }
   374      c := &timerCtx{
   375          cancelCtx: newCancelCtx(parent),
   376          deadline:  deadline,
   377      }
   ......

可以清晰的看到,当派生出的子 Context 的deadline在父Context之后,直接返回了一个父Context的拷贝。故语义上等效为父。

WithDeadline 的最后期限调整为不晚于 d 返回父上下文的副本。如果父母的截止日期已经早于 d,WithDeadline (父,d) 是在语义上等效为父。返回的上下文完成的通道关闭的最后期限期满后,返回的取消函数调用时,或当父上下文完成的通道关闭,以先发生者为准。

看看官方例子:

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    d := time.Now().Add(50 * time.Millisecond)
    ctx, cancel := context.WithDeadline(context.Background(), d)

    // Even though ctx will be expired, it is good practice to call its
    // cancelation function in any case. Failure to do so may keep the
    // context and its parent alive longer than necessary.
    defer cancel()

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err())
    }
}

WithTimeout 例子

WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))。

   436  func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
   437      return WithDeadline(parent, time.Now().Add(timeout))
   438  }

看看官方例子:

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // Pass a context with a timeout to tell a blocking function that it
    // should abandon its work after the timeout elapses.
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // prints "context deadline exceeded"
    }
}

WithValue 例子

   454  func WithValue(parent Context, key, val interface{}) Context {
   454      if key == nil {
   455          panic("nil key")
   456      }
   457      if !reflect.TypeOf(key).Comparable() {
   458          panic("key is not comparable")
   459      }
   460      return &valueCtx{parent, key, val}
   461  }

WithValue 返回的父与键关联的值在 val 的副本。

使用上下文值仅为过渡进程和 Api 的请求范围的数据,而不是将可选参数传递给函数。

提供的键必须是可比性和应该不是字符串类型或任何其他内置的类型以避免包使用的上下文之间的碰撞。WithValue 用户应该定义自己的键的类型。为了避免分配分配给接口 {} 时,上下文键经常有具体类型结构 {}。另外,导出的上下文关键变量静态类型应该是一个指针或接口。

看看官方例子:

package main

import (
    "context"
    "fmt"
)

func main() {
    type favContextKey string

    f := func(ctx context.Context, k favContextKey) {
        if v := ctx.Value(k); v != nil {
            fmt.Println("found value:", v)
            return
        }
        fmt.Println("key not found:", k)
    }

    k := favContextKey("language")
    ctx := context.WithValue(context.Background(), k, "Go")

    f(ctx, k)
    f(ctx, favContextKey("color"))
}

参考连接

[1] https://segmentfault.com/a/1190000006744213
[2] http://www.01happy.com/golang-context-reading/

 

 

 

我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。

谋胆并重

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Golang高并发:生产者消费者模型
Golang高并发:生产者消费者模型
568 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10089 0
【直播预告】阿里高级开发工程师加多:深度剖析Java并发编程系列(第二讲)Java并发模型&Java内存模型
Java并发模型:三种线程实现模型,用户级线程,内核级线程,两级线程模型。 Java内存模型:Java内存模型,计算机硬件内存模型,两者之间对应关系以及存在的问题。
2370 0
Go协程并发之百万级并发「让我们一起Golang」
Go协程并发之百万级并发「让我们一起Golang」
252 0
百万级高并发mongodb集群性能数十倍提升优化实践(下篇)
本文是oppo互联网某百亿级数据量/百万级高并发mongodb集群线上真实优化案例,荣获mongodb中文社区2019年度一等奖,上篇地址:https://developer.aliyun.com/article/779084?spm=a2c6h.13148508.0.0.37d34f0e4AZoIg
368 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13892 0
golang并发ping主机
版权声明:本文可能为博主原创文章,若标明出处可随便转载。 https://blog.
2006 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
11893 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
7365 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
22406 0
+关注
sunsky303
php、go、linux、异步编程、geeker、hacker
123
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载