乐趣区

关于golang:一切皆有可能Golang中的ThreadLocal库

开源仓库: 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() 返回了主协程 1AllGoids() 返回了主协程及协程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)

获取以后 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

退出移动版