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高并发程序设计]. 无锁是一种乐观的策略, 它假设对资源的访问是没 ...
随机推荐
- WPF用DirectSound播放声音
示例代码: var fileName = @"D:\WindowsLogon.wav"; DevicesCollection sound_devices = new Devices ...
- 三星860 evo 250g 开启AHCI模式读写对比
主板比较老,只支持sata2接口 换用三星860evo后跑分对比
- 腾讯移动Web整体解决方案Spirit
Spirit(勇气号),美国航天局NASA派往Mars(火星)的第一艘探测器.移动Web开发是一块新的领域,甚至有很多坑,这一点与人类从未踏上的Mars(火星)相似.为了避免开发者重复遇到相同的问题, ...
- 对Qt for Android的评价(很全面,基本已经没有问题了),可以重用QT积累20年的RTL是好事,QML效率是HTML5的5倍
现在Qt不要光看跨平台了,Qt也有能力和原生应用进行较量的.可以直接去Qt官网查看他和那些厂商合作.关于和Java的比较,框架和Java进行比较似乎不且实际.如果是C++和Java比较,网上有很多文章 ...
- Delphi各种从文件里读取内容的方法
Hi I am having a problem running a function to read a text file the problem seems to be that my anti ...
- 如何自学PHP做一个网站 PHP可以做什么项目?网站 小程序 公众号能用PHP开发吗?
很多想从事程序开发的人员,想自学一门语言,不知道从哪里下手学习,如何入门学习?今天我们就以PHP为例子,来讲述一下如何快速的学习一门开发语言,让你快速入门.PHP是一个什么语言?它能开发什么项目呢?下 ...
- 从电子游戏到DevOps
在一个项目团队中,开发与运维之间的关系像极了知名大型游戏<刺客信条>里的故事:开发就是追求自由的刺客联盟——我喜欢用各种新颖技术手段去满足用户爸爸那些花里胡哨的需求,你别管那技术好不好用, ...
- 【练习题】proj1 判断二叉树子树和是否为指定的值
#include <stdio.h> #include <vector> #include <list> #include<iostream> usin ...
- UI-grid 表格内容可编辑(enableCellEdit可指定列编辑)
在网上搜索了很多关于UI-Grid的问题 很遗憾好少啊啊啊 不过有API还是比较欣慰的 官方API:UI Grid 还有一位大佬的翻译的中文API:angularjs ui-grid中文api 行编辑 ...
- 确认过眼神,看清HTTP协议
导读:什么是 HTTP?它有什么属性?我们常用的是什么呢?快来阅读本文,将会为你一一道来. 什么是 HTTP 协议? 在了解HTTP之前,我们需要了解什么是网络通信模型(也就是我们常说的 OSI 模型 ...