Go语言Context(设计及分析)
context简单概述:Go服务器的每个请求都有自己的goroutine,而有的请求为了提高性能,会经常启动额外的goroutine处理请求,当该请求被取消或超时,该请求上的所有goroutines应该退出,防止资源泄露。那么context来了,它对该请求上的所有goroutines进行约束,然后进行取消信号,超时等操作。而context优点就是简洁的管理goroutines的生命周期。context简单使用: 接下来模拟一个超时继续分析context注意: 使用时遵循context规则
1. 不要将 Context放入结构体,Context应该作为第一个参数传
入,命名为ctx。
2. 即使函数允许,也不要传入nil的 Context。如果不知道用哪种
Context,可以使用context.TODO()。
3. 使用context的Value相关方法,只应该用于在程序和接口中传递
和请求相关数据,不能用它来传递一些可选的参数
4. 相同的 Context 可以传递给在不同的goroutine;Context 是
并发安全的。使用net/http/pprof对goroutines进行查看:
package main import (
"context"
"fmt"
"net/http"
_ "net/http/pprof"
"time"
) func main() {
go http.ListenAndServe(":8080", nil)
ctx, _ := context.WithTimeout(context.Background(), (10 * time.Second))
go testA(ctx)
select {}
} func testA(ctx context.Context) {
ctxA, _ := context.WithTimeout(ctx, (5 * time.Second))
ch := make(chan int)
go testB(ctxA, ch) select {
case <-ctx.Done():
fmt.Println("testA Done")
return
case i := <-ch:
fmt.Println(i)
} } func testB(ctx context.Context, ch chan int) {
//模拟读取数据
sumCh := make(chan int)
go func(sumCh chan int) {
sum := 10
time.Sleep(10 * time.Second)
sumCh <- sum
}(sumCh) select {
case <-ctx.Done():
fmt.Println("testB Done")
<-sumCh
return
//case ch <- <-sumCh: 注意这样会导致资源泄露
case i := <-sumCh:
fmt.Println("send", i)
ch <- i
} }
模拟数据库读取效率慢从执行中和执行后的结果来看,我们完美的关闭了不需要的goroutine,
超时概述中提到:
当应用场景是由一个请求衍生出多个goroutine完成需求,那它们之间就需要满足一定的约束关系,才能中止routine树,超时等操作。
那么是如何到达约束关系?
简单理解,Context 的调用以链式存在,通过WithXxx方法派生出新的 Context与当前父Context 关联,当父 Context 被取消时,其派生的所有 Context 都将取消。
WithCancel派生约束关系图上图WithCancel派生简单分析图,接下里我们进行源码分析,进一步了解Context的约束关系(WithXxx方法派生大概与WithCancel相同)。
Context源码分析:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}Deadline() 返回的time.Time 它为Context 的结束的时间,ok 表示是否有 deadline
Done() 返回一个信道,当对Context进行撤销或过期时,该信道就会关闭的,可以简单认为它是关闭信号。
Err() 当Done信道关闭后,Err会返回关闭的原因(如超时,手动关闭)Value(key interface{}) 一个 K-V 存储的方法
canceler提供了cancal函数,同时要求数据结构实现Context
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}//默认错误
var Canceled = errors.New("context canceled")
var DeadlineExceeded = errors.New("context deadline exceeded")实现Context的数据结构
type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
} func (*emptyCtx) Done() <-chan struct{} {
return nil
} func (*emptyCtx) Err() error {
return nil
} func (*emptyCtx) Value(key interface{}) interface{} {
return nil
} func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}两个实现Context的空结构。
var (
background = new(emptyCtx)
todo = new(emptyCtx)
) func Background() Context {
return background
} func TODO() Context {
return todo
}cancelCtx结构体继承了Context,同时实现了canceler接口:
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]bool // set to nil by the first cancel call
err error // 当被cancel时将会把err设置为非nil
} func (c *cancelCtx) Done() <-chan struct{} {
return c.done
} func (c *cancelCtx) Err() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.err
} func (c *cancelCtx) String() string {
return fmt.Sprintf("%v.WithCancel", c.Context)
} func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
//timerCtx会频繁使用这块代码,因为派生出来
//timerCtx全部指向同一个cancelCtx.
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
//关闭c.done
close(c.done)
//依次遍历c.children,每个child分别cancel
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
//如果removeFromParent为true,则将c从其parent的children中删除
if removeFromParent {
removeChild(c.Context, c)
}
}
//如果parent为valueCtx类型,将循环找最近parent
//为CancelCtx类型的,找到就从父对象的children
//map 中删除这个child,否则返回nil(context.Background或者 context.TODO)
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}接下来分析Cancel相关代码
type CancelFunc func() //WithCancel方法返回一个继承parent的Context对
//象,同时返回的cancel方法可以用来关闭当前
//Context中的Done channel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
} func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{
Context: parent,
done: make(chan struct{}),
}
} func propagateCancel(parent Context, child canceler) {
if parent.Done() == nil {
return // parent is never canceled
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
//如果找到的parent已经被cancel,
//则将方才传入的child树给cancel掉
child.cancel(false, p.err)
} else {
//否则, 将child节点直接添加到parent的children中
//(这样就有了约束关系,向上的父亲指针不变
//,向下的孩子指针可以直接使用 )
if p.children == nil {
p.children = make(map[canceler]bool)
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
//如果没有找到最近的可以被cancel的parent,
//则启动一个goroutine,等待传入的parent终止,
//并cancel传入的child树,或者等待传入的child终结。
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
} //判断parent是否为cancelCtx类型
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx:
return c, true
case *timerCtx:
return &c.cancelCtx, true
case *valueCtx:
parent = c.Context
default:
return nil, false
}
}
}timerCtx 继承cancelCtx的结构体,这种设计就可以避免写重复代码,提高复用
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu. 计时器
deadline time.Time
} func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
} func (c *timerCtx) String() string {
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
} // 与cencelCtx有所不同,除了处理cancelCtx.cancel,
// 还回对c.timer进行Stop(),并将c.timer=nil
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}timerCtx具体的两个方法:
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
//如果parent的deadline比新传入的deadline早,则直接
//返回WithCancel,因为parent的deadline会先失效,而新的
//deadline根据不需要
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: deadline,
}
propagateCancel(parent, c) //检查如果已经过期,则cancel新child树
d := deadline.Sub(time.Now())
if d <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(true, Canceled) }
} c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
//没有被cancel的话,就设置deadline之后cancel的计时器
c.timer = time.AfterFunc(d, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
} // WithTimeout简单暴力,直接把WithTimeout返回
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}valueCtx主要用来传递一些可比较操作的数据
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflect.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val} type valueCtx struct {
Context
key, val interface{}
} func (c *valueCtx) String() string {
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
} func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
Go语言Context(设计及分析)的更多相关文章
- C++ 语言特性的性能分析
转载:http://www.cnblogs.com/rollenholt/archive/2012/05/07/2487244.html 大多数开发人员通常都有这个观点,即汇编语言和 C 语 ...
- PM2.5环境检测系统的设计与分析
PM2.5环境检测系统的设计与分析 摘要: 大气颗粒物污染对人类健康和生态环境造成了很大的影响,这让人们逐渐重视起对细颗粒物PM2.5检测技术的研究.本文阐述了PM2.5浓度检测的五种方法,在对上 ...
- 【转】Swift 语言的设计错误
Swift 语言的设计错误 在『编程的智慧』一文中,我分析和肯定了 Swift 语言的 optional type 设计,但这并不等于 Swift 语言的整体设计是完美没有问题的.其实 Swift 1 ...
- Go 语言机制之逃逸分析
https://blog.csdn.net/weixin_38975685/article/details/79788254 Go 语言机制之逃逸分析 https://blog.csdn.net/ ...
- c++学习书籍推荐《C++语言的设计与演化》下载
百度云及其他网盘下载地址:点我 编辑推荐 <C++语言的设计与演化>由C++语言的设计者Bjarne Stroustrup著就,是一本阐述C++语言的设计及开发过程的无可争辩的内情手册.S ...
- Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战
Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战 说明: Java生鲜系统中微服务的拆分应该如何架构设计与分析呢?以下是我的实战中的设计与经验分析. 目录 1. 微服务简介2. 当前现状3. ...
- C语言课程设计
目录 实现目的 游戏玩法介绍 实现流程与作品架构 任务列表及贡献度 总结感想 作品源码与仓库地址(附页) 资料引用与出处(附页) 实现目的 2048,作为一款极其经典的游戏,从发行到现在,已经有了极多 ...
- php开发面试题---游戏面向对象设计与分析实例
php开发面试题---游戏面向对象设计与分析实例 一.总结 一句话总结: 不要光空想,多看几个实例就知道自己的游戏该怎么设计了 根据实例去理解面向对象编程的的六大原则 1.英雄种类分别有:战士.法师. ...
- 学写PEP,参与Python语言的设计
如果你为Python写了一篇PEP,这篇PEP成功的被Python指导委员会接受了,那么以后你在吹牛皮的时候你就可以说我主导了Python语言某个特性的设计工作. -- 跬蟒 我就问你主导Python ...
随机推荐
- LeetCode(41)-Rectangle Area
题目: Find the total area covered by two rectilinear rectangles in a 2D plane. Each rectangle is defin ...
- Mac OS X 简单的方法知道何时来电了
最近本猫所在的小区时常停电,往往半夜或是凌晨才来电啊!早上起来本猫在想如何知道确切的来电时间,但又不费事的方法呢. 方法一是用手机录音器录音,因为来电后门禁会发出"滴"的一声,所以 ...
- ARP 协议抓包分析
ARP(Address Resolution Protocol)- 地址解析分析 ARP 协议是根据IP地址获取物理地址的一个TCP/IP协议. 当PC1 想与 PC2 进行通信时,需要同时知道PC2 ...
- 学习MQ(三) 一个实例
学习MQ(三) 一个实例. 现在有两台机器A和B,分别安装了MQ6.0,我要通过MQ进行A和B之间的双向通信. 我打算分两步,第一步:实现A到B的数据传输. 在A上: 1.创建队列管理器 QM_100 ...
- Java——面向对象 this关键字
this,当成员变量和局部变量名字重名时,可以用关键字来区分. this 代表当前对象,就是所在函数所属的对象的引用. 即哪个调用了this所在的函数,this就代表哪个函数. 应用:1,构造方法间的 ...
- 在vs2013下手把手创建/调用dll
body { font: 16px } 参考了大佬的文章 首先,体会一下静态编译: 创建Win32Project,选DLL,添加一个.h和.cpp文件 点击生成解决方案,然后去debug目录下拷贝.l ...
- Neo4j安装后的密码修改
首先默认用户名/密码是neo4j/neo4j. 在安全验证打开的时候,你访问服务器/db/data之类的地址可能会提示您以下信息: { "password_change" : &q ...
- 推荐个Mac OSX下的Code Editor:Atom
首先只是当Editor用,不是整成IDE级. 先说几个大家耳熟能详的: 1.Sublime,Sublime在Mac下的安装并不完全,CLI启动需要自己ln个链接.还有一些其他原因,比如Packages ...
- Java并发之AQS详解
一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...
- Myeclipse+selenium2.0+Junit+TestNg环境搭建
这周末把自动化的环境搭好了,在网上也百度了很多,现在分享下,希望大家少走一点歪路. 需要用到的安装包都在这个里面,自取: 链接:https://pan.baidu.com/s/10ohf757ztgN ...