开源仓库: https://github.com/go-eden/ro...

本文介绍的是新写的routine库,它封装并提供了一些易用、高性能的goroutine上下文拜访接口,能够帮忙你更优雅地拜访协程上下文信息,但你也可能就此关上了潘多拉魔盒。

介绍

Golang语言从设计之初,就始终在不遗余力地向开发者屏蔽协程上下文的概念,包含协程goid的获取、过程外部协程状态、协程上下文存储等。

如果你应用过其余语言如C++/Java等,那么你肯定很相熟ThreadLocal,而在开始应用Golang之后,你肯定会为短少相似ThreadLocal的便捷性能而深感困惑与苦恼。 当然你能够抉择应用Context
,让它携带着全副上下文信息,在所有函数的第一个输出参数中呈现,而后在你的零碎中到处穿梭。

routine的外围指标就是开拓另一条路:将goroutine local storage引入Golang世界,同时也将协程信息裸露进去,以满足某些人人可能有的需要。

应用演示

此章节简要介绍如何装置与应用routine库。

装置

go get github.com/go-eden/routine

应用goid

以下代码简略演示了routine.Goid()routine.AllGoids()的应用:

package mainimport (    "fmt"    "github.com/go-eden/routine"    "time")func main() {    go func() {        time.Sleep(time.Second)    }()    goid := routine.Goid()    goids := routine.AllGoids()    fmt.Printf("curr goid: %d\n", goid)    fmt.Printf("all goids: %v\n", goids)}

此例中main函数启动了一个新的协程,因而Goid()返回了主协程1AllGoids()返回了主协程及协程18:

curr goid: 1all goids: [1 18]

应用LocalStorage

以下代码简略演示了LocalStorage的创立、设置、获取、跨协程流传等:

package mainimport (    "fmt"    "github.com/go-eden/routine"    "time")var nameVar = routine.NewLocalStorage()func main() {    nameVar.Set("hello world")    fmt.Println("name: ", nameVar.Get())    // other goroutine cannot read nameVar    go func() {        fmt.Println("name1: ", nameVar.Get())    }()    // but, the new goroutine could inherit/copy all local data from the current goroutine like this:    routine.Go(func() {        fmt.Println("name2: ", nameVar.Get())    })    // or, you could copy all local data manually    ic := routine.BackupContext()    go func() {        routine.InheritContext(ic)        fmt.Println("name3: ", nameVar.Get())    }()    time.Sleep(time.Second)}

执行后果为:

name:  hello worldname1:  <nil>name3:  hello worldname2:  hello world

API文档

此章节具体介绍了routine库封装的全副接口,以及它们的外围性能、实现形式等。

Goid() (id int64)

获取以后goroutinegoid

在失常状况下,Goid()优先尝试通过go_tls的形式间接获取,此操作极快,耗时通常只相当于rand.Int()的五分之一。

若呈现版本不兼容等谬误时,Goid()会尝试从runtime.Stack信息中解析获取,此时性能会呈现指数级的损耗,即变慢约一千倍,但能够保障性能失常可用。

AllGoids() (ids []int64)

获取以后过程全副沉闷goroutinegoid

go 1.15及更旧的版本中,AllGoids()会尝试从runtime.Stack信息中解析获取全副协程信息,但此操作十分低效,十分不倡议在高频逻辑中应用。

go 1.16之后的版本中,AllGoids()会通过native的形式间接读取runtime的全局协程池信息,在性能上失去了极大的进步, 但思考到生产环境中可能有万、百万级的协程数量,因而仍不倡议在高频应用它。

NewLocalStorage():

创立一个新的LocalStorage实例,它的设计思路与用法和其余语言中的ThreadLocal十分类似。

BackupContext() *ImmutableContext

备份以后协程上下文的local storage数据,它只是一个便于上下文数据传递的不可变构造体。

InheritContext(ic *ImmutableContext)

被动继承备份到的上下文local storage数据,它会将其余协程BackupContext()的数据复制入以后协程上下文中,从而反对跨协程的上下文数据流传

Go(f func())

启动一个新的协程,同时主动将以后协程的全副上下文local storage数据复制至新协程,它的外部实现由BackupContext()InheritContext()组成。

LocalStorage

示意协程上下文变量,反对的函数包含:

  • Get() (value interface{}):获取以后协程已设置的变量值,若未设置则为nil
  • Set(v interface{}) interface{}:设置以后协程的上下文变量值,返回之前已设置的旧值
  • Del() (v interface{}):删除以后协程的上下文变量值,返回已删除的旧值
  • Clear():彻底清理此上下文变量在所有协程中保留的旧值

垃圾回收

routine库外部保护了全局的storages,它存储了全副协程的全副变量值,在读写时基于goroutinegoidLocalStorage进行数据惟一映射。

在过程的整个生命周期中,可能呈现有无数个协程的创立与销毁,
因而有必要被动清理dead协程在全局storages中缓存的上下文数据。
这个工作由routine库中的一个全局定时器执行,它会在必要的时候,
每隔一段时间扫描并清理dead协程的相干信息,以防止可能呈现的内存泄露隐患。

License

MIT