Golang 高效实践之并发实践context篇
前言
在上篇Golang高效实践之并发实践channel篇中我给大家介绍了Golang并发模型,详细的介绍了channel的用法,和用select管理channel。比如说我们可以用channel来控制几个goroutine的同步和退出时机,但是我们需要close channel通知其他接受者,当通知和通信的内容混在一起时往往比较复杂,需要把握好channel的读写时机,以及不能往已经关闭的channel中再写入数据。如果有没有一种更好的上下文控制机制呢?答案就是文章今天要介绍的context,context正是close channel的一种封装,通常用来控制上下文的同步。
Context介绍
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)
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 :=
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 == {
break
}
}
}
WithDeadline函数
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
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( * 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( * 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)
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(), *time.Millisecond)
defer cancel() select {
case <-time.After( * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
} }
Background函数
func Background() Context
Backgroud函数返回一个非nil的空context。该context不会cancel,没有值,没有deadline。通常在main函数中调用,初始化或者测试,作为顶级的context。
WithValue函数
func WithValue(parent Context, key, val interface{}) Context
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 = // 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/
Golang 高效实践之并发实践context篇的更多相关文章
- Golang 高效实践之并发实践
前言 在我前面一篇文章Golang受欢迎的原因中已经提到,Golang是在语言层面(runtime)就支持了并发模型.那么作为编程人员,我们在实践Golang的并发编程时,又有什么需要注意的点呢?下面 ...
- Golang高效实践之泛谈篇
前言 我博客之前的Golang高效实践系列博客中已经系统的介绍了Golang的一些高效实践建议,例如: <Golang高效实践之interface.reflection.json实践>&l ...
- Struts2、Spring、Hibernate 高效开发的最佳实践(转载)
Struts2.Spring.Hibernate 高效开发的最佳实践 Struts2.Spring.Hibernate(SSH)是最常用的 Java EE Web 组件层的开发技术搭配,网络中和许多 ...
- 心知天气数据API 产品的高并发实践
心知天气数据API 产品的高并发实践 心知天气作为国内领先的商业气象服务提供商,天气数据API 产品从公司创立以来就一直扮演着很重要的角色.2009 年API 产品初次上线,历经十年,我们不断用心迭代 ...
- Golang在京东列表页实践总结
Golang在京东列表页实践总结 作者:张洪涛 10余年软件开发和设计经验,曾就职于搜狐.搜狗.前matrixjoy公司联合创始人.甘普科技CTO. 目前线上状态 基于搜索实现: 全量数据,搜索结果不 ...
- 【Scala】Scala多线程-并发实践
Scala多线程-并发实践 scala extends Thread_百度搜索 scala多线程 - 且穷且独立 - 博客园 Scala和并发编程 - Andy Tech Talk - ITeye博客 ...
- 2.高并发教程-基础篇-之nginx+mysql实现负载均衡和读写分离
技巧提示:mysql读写分离搭建好之后,配合nginx的负载均衡,可以高效的mysql的集群性能,同时免去麻烦的query分流.比如,sever1收到的请求就专门链接slave1从mysql读取数据, ...
- 3.高并发教程-基础篇-之分布式全文搜索引擎elasticsearch的搭建
高并发教程-基础篇-之分布式全文搜索引擎elasticsearch的搭建 如果大家看了我的上一篇<2.高并发教程-基础篇-之nginx+mysql实现负载均衡和读写分离>文章,如果能很好的 ...
- Java并发 - (无锁)篇6
, 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 本文主要介绍了死锁的概念与一些相关的基础类, 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 无锁是一种乐观的策略, 它假设对资源的访问是没 ...
随机推荐
- 关于git远程分支操作
对于用户来说,git给人提交到本地的机会.我们可以在自己的机器上创建不同的branch,来测试和存放不同的代码. 对于代码管理员而言,git有许多优良的特性.管理着不同的分支,同一套源代码可以出不一样 ...
- SharePoint Add-in Model (App Model) 介绍 – 概念、托管方式、开发语言
SharePoint Add-in Model 是自 2013 版本以来引入的新的扩展性开发模型, SharePoint 开发者可以利用这种新模型来实现往常利用场解决方案 (Farm Solution ...
- zyltimer与ZylIdleTimer
http://www.zylsoft.com/zyltimer.htmhttp://www.zylsoft.com/products.htm
- delphi LPT1端口打印与开钱箱
{设置打印机}Assignfile(RPrinter,'LPT1'); {准备写文件}Rewrite(RPrinter); {向后倒纸}//Writeln(RPrinter,chr($b)+chr(2 ...
- uni-app $refs的基本用法
$refs的基本用法 一个对象(Object),持有注册过 ref 特性 的所有 DOM 元素和组件实例. <template> <view class="containe ...
- log4net插入access自定义字段
1.创建表格 2.创建log4net.xml,并设置属性始终复制,关键属性 <bufferSize value="1" /> <conversionPattern ...
- MCtalk对话学吧课堂:真正的K12在线教育才刚刚开始
课堂之外的在线教育已经被大部分家庭所熟知,既涌现出了VIPKID等行业独角兽,也有大量致力于科技改变教育的新兴机构获得了快速成长.成立于2014年的学吧课堂就是专注在K12在线教育领域的创新机构,他们 ...
- VUE、微信for动态变量取值(拼接取值)
item.value是其它循的值如value=[1,2,3] {{'images[arrAy' + item.value+']'}} 那么拼接结果是 {{images[arrAy1]}}, {{ima ...
- 【对象属性复制】BeanUtils.copyProperties(obj1, obj2);
实现对象的属性值复制,只会复制命名相同的文件. import org.springframework.beans.BeanUtils; BeanUtils.copyProperties(obj1, o ...
- 获取Spring中的Bean
1.Utils工具类 package com.xxx.common.helper; import org.springframework.beans.BeansException; import or ...