開源倉庫: 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()
返回了主協程1
,AllGoids()
返回了主協程及協程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
此章節詳細介紹了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
協程的相關信息,以免可能出現的內存泄露隱患。
MIT