共计 2738 个字符,预计需要花费 7 分钟才能阅读完成。
开源仓库: 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 main
import (
"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()
返回了主协程 1
,AllGoids()
返回了主协程及协程18
:
curr goid: 1
all goids: [1 18]
应用LocalStorage
以下代码简略演示了 LocalStorage
的创立、设置、获取、跨协程流传等:
package main
import (
"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 world
name1: <nil>
name3: hello world
name2: hello world
API 文档
此章节具体介绍了 routine
库封装的全副接口,以及它们的外围性能、实现形式等。
Goid() (id int64)
获取以后 goroutine
的goid
。
在失常状况下,Goid()
优先尝试通过 go_tls
的形式间接获取,此操作极快,耗时通常只相当于 rand.Int()
的五分之一。
若呈现版本不兼容等谬误时,Goid()
会尝试从 runtime.Stack
信息中解析获取,此时性能会呈现指数级的损耗,即变慢约一千倍,但能够保障性能失常可用。
AllGoids() (ids []int64)
获取以后过程全副沉闷 goroutine
的goid
。
在 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
,它存储了全副协程的全副变量值,在读写时基于goroutine
的goid
和 LocalStorage
进行数据惟一映射。
在过程的整个生命周期中,可能呈现有无数个协程的创立与销毁,
因而有必要被动清理 dead
协程在全局 storages
中缓存的上下文数据。
这个工作由 routine
库中的一个全局定时器执行,它会在必要的时候,
每隔一段时间扫描并清理 dead
协程的相干信息,以防止可能呈现的内存泄露隐患。
License
MIT