Golang 高效实践之并发实践context篇

前言

在上篇Golang高效实践之并发实践channel篇中我给大家介绍了Golang并发模型,详细的介绍了channel的用法,和用select管理channel。比如说我们可以用channel来控制几个goroutine的同步和退出时机,但是我们需要close channel通知其他接受者,当通知和通信的内容混在一起时往往比较复杂,需要把握好channel的读写时机,以及不能往已经关闭的channel中再写入数据。如果有没有一种更好的上下文控制机制呢?答案就是文章今天要介绍的context,context正在close channel的一种封装,通常用来控制上下文的同步。

Context介绍

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC" }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue"; min-height: 14.0px }
p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue" }
span.s1 { font: 12.0px "Helvetica Neue" }
span.s2 { font: 12.0px ".PingFang SC" }
span.Apple-tab-span { white-space: pre }

Context包定义了Context类型,Context类型携带着deadline生命周期,和取消信号,并且可以携带用户自定义的参数值。通常用Context来控制上下文,Context通过参数一层层传递,或者传递context的派生,一旦Context被取消,所有由该Context派生的Context也会取消。WithCancel,WithDeadline,和WithTimeout函数可以从一个Context中派生另外一个Context和一个cancel函数。调用cancel函数可以取消由context派生出来的Context。cancel函数会释放context拥有的资源,所以当context不用时要尽快调用cancel。

Context应该作为函数的第一个参数,通常使用ctx命名,例如:

func DoSomething(ctx context.Context, arg Arg) error {

// … use ctx …

}

不要传递nil context,即便接受的函数允许我们这样做也不要传递nil context。如果你不确定用哪个context的话可以传递context.TODO。

同一个context可以在不同的goroutine中访问,context是线程安全的。

Context结构定义

type Context interface {
        // Deadline returns the time when work done on behalf of this context
        // should be canceled. Deadline returns ok==false when no deadline is
        // set. Successive calls to Deadline return the same results.
        Deadline() (deadline time.Time, ok bool)

        // Done returns a channel that‘s closed when work done on behalf of this
        // context should be canceled. Done may return nil if this context can
        // never be canceled. Successive calls to Done return the same value.
        //
        // WithCancel arranges for Done to be closed when cancel is called;
        // WithDeadline arranges for Done to be closed when the deadline
        // expires; WithTimeout arranges for Done to be closed when the timeout
        // elapses.
        //
        // Done is provided for use in select statements:
        //
        //  // Stream generates values with DoSomething and sends them to out
        //  // until DoSomething returns an error or ctx.Done is closed.
        //  func Stream(ctx context.Context, out chan<- Value) error {
        //      for {
        //          v, err := DoSomething(ctx)
        //          if err != nil {
        //              return err
        //          }
        //          select {
        //          case <-ctx.Done():
        //              return ctx.Err()
        //          case out <- v:
        //          }
        //      }
        //  }
        //
        // See https://blog.golang.org/pipelines for more examples of how to use
        // a Done channel for cancelation.
        Done() <-chan struct{}

        // If Done is not yet closed, Err returns nil.
        // If Done is closed, Err returns a non-nil error explaining why:
        // Canceled if the context was canceled
        // or DeadlineExceeded if the context‘s deadline passed.
        // After Err returns a non-nil error, successive calls to Err return the same error.
        Err() error

        // Value returns the value associated with this context for key, or nil
        // if no value is associated with key. Successive calls to Value with
        // the same key returns the same result.
        //
        // Use context values only for request-scoped data that transits
        // processes and API boundaries, not for passing optional parameters to
        // functions.
        //
        // A key identifies a specific value in a Context. Functions that wish
        // to store values in Context typically allocate a key in a global
        // variable then use that key as the argument to context.WithValue and
        // Context.Value. A key can be any type that supports equality;
        // packages should define keys as an unexported type to avoid
        // collisions.
        //
        // Packages that define a Context key should provide type-safe accessors
        // for the values stored using that key:
        //
        //     // Package user defines a User type that‘s stored in Contexts.
        //     package user
        //
        //     import "context"
        //
        //     // User is the type of value stored in the Contexts.
        //     type User struct {...}
        //
        //     // key is an unexported type for keys defined in this package.
        //     // This prevents collisions with keys defined in other packages.
        //     type key int
        //
        //     // userKey is the key for user.User values in Contexts. It is
        //     // unexported; clients use user.NewContext and user.FromContext
        //     // instead of using this key directly.
        //     var userKey key
        //
        //     // NewContext returns a new Context that carries value u.
        //     func NewContext(ctx context.Context, u *User) context.Context {
        //         return context.WithValue(ctx, userKey, u)
        //     }
        //
        //     // FromContext returns the User value stored in ctx, if any.
        //     func FromContext(ctx context.Context) (*User, bool) {
        //         u, ok := ctx.Value(userKey).(*User)
        //         return u, ok
        //     }
        Value(key interface{}) interface{}
}

WithCancel函数

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue" }
span.s1 { font: 12.0px ".PingFang SC" }

WithCancel函数返回parent的一份拷贝和一个新的Done channel。当concel 函数被调用的时候或者parent的Done channel被关闭时(cancel被调用),context的Done channel将会被关闭。取消context将会释放context相关的资源,所以当context完成时代码应该尽快调用cancel方法。例如:

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函数

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue" }
span.s1 { font: 12.0px ".PingFang SC" }

WithDeadline函数返回parent context调整deadline之后的拷贝,如果parent的deadline比要调整的d更早,那么派生出来的context的deadline就等于parent的deadline。当deadline过期或者cancel函数被调用时,又或者parent的cancel函数被调用时,context的Done channel将会被触发。例如:

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())
    }

}

Err方法会返回context退出的原因,这里是context deadline exceeded。

WithTimeout函数

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue" }
span.s1 { font: 12.0px ".PingFang SC" }

WithTimeout相当于调用WithDeadline(parent, time.Now().Add(timeout)),例如:

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"
    }

}

Background函数

func Background() Context

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue" }
span.s1 { font: 12.0px ".PingFang SC" }

Backgroud函数返回一个非nil的空context。该context不会cancel,没有值,没有deadline。通常在main函数中调用,初始化或者测试,作为顶级的context。

WithValue函数

func WithValue(parent Context, key, val interface{}) Context

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue" }
span.s1 { font: 12.0px ".PingFang SC" }

WithValue函数返回parent的拷贝,并且key对应的值是value。例如:

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"))

}

webserver实战

有了上面的理论知识后,我将给大家讲解一个webserver的编码,其中就用到context的超时特性,以及上下文同步等。代码放在github上面,是从google search仓库中fork出来并做了一些改动。该项目的代码用到go module来组织代码,如果对go module不熟悉的同学可以参考我的这篇博客

server.go文件是main包,里面包含一个http server:

func main() {
    http.HandleFunc("/search", handleSearch)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

例如通过/search?q=golang&timeout=1s访问8080端口将会调用handle函数handleSearch来处理,handleSearch会解析出来要查询的关键字golang,并且指定的超时时间是1s。该timeout参数会用于生成带有timeout属性的context,该context会贯穿整个请求的上下文,当超时时间触发时会终止search。

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())
    }
    defer cancel() // Cancel ctx as soon as handleSearch returns.

并且使用WithValue函数传递客户端的IP:

const userIPKey key = 0

// NewContext returns a new Context carrying userIP.
func NewContext(ctx context.Context, userIP net.IP) context.Context {
    return context.WithValue(ctx, userIPKey, userIP)
}

google包里面的Search函数实际的动作是将请求的参数传递给https://developers.google.com/custom-search,并且带上context的超时属性,当context超时的时候将会直接返回,不会等待https://developers.google.com/custom-search的返回。实际效果:

超时情况:

非超时情况:

总结

文章介绍了Golang的context包,并且介绍了包里面的主要函数和作用,最后通过一个练习项目示例了context的实际应用。

参考

https://blog.golang.org/context

https://golang.org/pkg/context/

原文地址:https://www.cnblogs.com/makelu/p/11215530.html

时间: 2024-10-06 09:40:05

Golang 高效实践之并发实践context篇的相关文章

Golang 高效实践之并发实践

前言 在我前面一篇文章Golang受欢迎的原因中已经提到,Golang是在语言层面(runtime)就支持了并发模型.那么作为编程人员,我们在实践Golang的并发编程时,又有什么需要注意的点呢?下面我会跟大家详细的介绍一些在实际生产编程中很容易踩坑的知识点. CSP 在介绍Golang的并发实践前,有必要先介绍简单介绍一下CSP理论.CSP,全称是Communicating sequential processes,翻译为通信顺序进程,又翻译为交换消息的顺序程序,用来描述并发性系统的交互模式.

我的大数据实践之路-洗脑篇

1. 什么是大数据 五个简单故事告诉你什么是"大数据" 2.如何看待大数据 要全体不要抽样,要效率不要绝对精确,要相关不要因果 3.大数据能干什么 通过用户的使用习惯来预判用户的行为 4.大数据应用场景 我的大数据实践之路-洗脑篇

3.高并发教程-基础篇-之分布式全文搜索引擎elasticsearch的搭建

高并发教程-基础篇-之分布式全文搜索引擎elasticsearch的搭建 如果大家看了我的上一篇<2.高并发教程-基础篇-之nginx+mysql实现负载均衡和读写分离>文章,如果能很好的利用,那么其实已经可以轻松日抗千万级别的访问量了,但是如果业务涉及查询比较多,查询条件比较丰富,又或者我就想要查询的响应更快点,那么在mysql上面去做优化,其实比较辛苦,有没有更好的解决方案呢?答案是肯定的!它就是我们今天的主角,分布式全文搜索引擎elasticsearch. 技巧提示:mysql集群层主要

[Android Memory] App调试内存泄露之Context篇(上)

转载自:http://www.cnblogs.com/qianxudetianxia/p/3645106.html Context作为最基本的上下文,承载着Activity,Service等最基本组件.当有对象引用到Activity,并不能被回收释放,必将造成大范围的对象无法被回收释放,进而造成内存泄漏. 下面针对一些常用场景逐一分析. 1. CallBack对象的引用 先看一段代码: @Override protectedvoid onCreate(Bundle state){ super.o

[Android Memory] App调试内存泄露之Context篇(下)

转载地址:http://www.cnblogs.com/qianxudetianxia/p/3655475.html 5. AsyncTask对象 我N年前去盛大面过一次试,当时面试官极力推荐我使用AsyncTask等系统自带类去做事情,当然无可厚非. 但是AsyncTask确实需要额外注意一下.它的泄露原理和前面Handler,Thread泄露的原理差不多,它的生命周期和Activity不一定一致. 解决方案是:在activity退出的时候,终止AsyncTask中的后台任务. 但是,问题是如

Python数据分析基础与实践 Python数据分析实践课程 Python视频教程

课程简介: Python数据分析基础与实践 Python数据分析实践课程 Python视频教程----------------------课程目录------------------------------├<章节1Python概况>├<章节2Python安装>├<章节3数据准备>├<章节4数据处理>├<章节5数据可视化>├<章节6网页数据抓取>├<章节7连接MySQL>├<章节8数据分析> 下载地址:百度网盘

golang的多协程实践

go语言以优异的并发特性而闻名,刚好手上有个小项目比较适合. 项目背景: 公司播控平台的数据存储包括MySQL和ElasticSearch(ES)两个部分,编辑.运营的数据首先保存在MySQL中,为了实现模糊搜索和产品关联推荐,特别增加了ES,ES中保存的是节目集的基本信息. 本项目是为了防止实时同步数据出现问题或者系统重新初始化时的全量数据同步而做的.项目主要是从MySQL读取所有的节目集数据写入到ES中. 项目特点: 因为节目集数量较大,不能一次性的读入内存,因此每次读出一部分记录写入ES.

高效运维最佳实践七字诀,不再憋屈的运维!

我们那么努力,为什么总感觉过得那么憋屈.苦闷?做的事情那么多,为什么业务部门.直接领导和公司貌似都那么不领情?怎么做才能自己更加开心些? 做运维的那么多,快乐的能有几个? 我们那么努力,为什么总感觉过得那么憋屈.苦闷?做的事情那么多,为什么业务部门.直接领导和公司貌似都那么不领情?怎么做才能自己更加开心些? 本专栏的主线实际是一个运维人员的十年成长史,从菜鸟到运维总监.但不是基础技术教学,也不会在运维技术的某一方面过深涉及.更多的是应用技巧.实践经验及案例剖析.专栏中的系列文章,包含作者在运维各

转 Web程序优化的最佳实践:JavaScript和CSS篇

Yahoo!的Exceptional Performance团队为改善Web性能带来最佳实践.他们为此进行了 一系列的实验.开发了各种工具.写了大量的文章和博客并在各种会议上参与探讨.最佳实践的核心就是旨在提高网站性能. Excetional Performance 团队总结出了一系列可以提高网站速度的方法.可以分为 7 大类 34 条.包括内容.服务器.cookie.CSS.JavaScript.图片.移动应用等七部分. 本文为CSS和Javascript部分: 除此之外,JavaScript