一切皆有可能——Golang中的」ThreadLocal「庫

開源倉庫: https://github.com/go-eden/ro...git

本文介紹的是新寫的routine庫,它封裝並提供了一些易用、高性能的goroutine上下文訪問接口,能夠幫助你更優雅地訪問協程上下文信息,但你也可能就此打開了潘多拉魔盒。github

介紹

Golang語言從設計之初,就一直在竭盡全力地向開發者屏蔽協程上下文的概念,包括協程goid的獲取、進程內部協程狀態、協程上下文存儲等。緩存

若是你使用過其餘語言如C++/Java等,那麼你必定很熟悉ThreadLocal,而在開始使用Golang以後,你必定會爲缺乏相似ThreadLocal的便捷功能而深感困惑與苦惱。 固然你能夠選擇使用Context
,讓它攜帶着所有上下文信息,在全部函數的第一個輸入參數中出現,而後在你的系統中處處穿梭。bash

routine的核心目標就是開闢另外一條路:將goroutine local storage引入Golang世界,同時也將協程信息暴露出來,以知足某些人人可能有的需求。函數

使用演示

此章節簡要介紹如何安裝與使用routine庫。性能

安裝

go get github.com/go-eden/routine

使用goid

如下代碼簡單演示了routine.Goid()routine.AllGoids()的使用:this

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的建立、設置、獲取、跨協程傳播等:code

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

相關文章
相關標籤/搜索