<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一切皆有可能——Golang 中的”ThreadLocal“庫

          共 4272字,需瀏覽 9分鐘

           ·

          2021-06-19 23:33

          作者:sisyphsu

          來源:SegmentFault 思否社區(qū)


          開源倉庫: 

          https://github.com/go-eden/routine

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

          介紹

          Golang語言從設計之初,就一直在不遺余力地向開發(fā)者屏蔽協(xié)程上下文的概念,包括協(xié)程goid的獲取、進程內(nèi)部協(xié)程狀態(tài)、協(xié)程上下文存儲等。

          如果你使用過其他語言如C++/Java等,那么你一定很熟悉ThreadLocal,而在開始使用Golang之后,你一定會為缺少類似ThreadLocal的便捷功能而深感困惑與苦惱。當然你可以選擇使用Context,讓它攜帶著全部上下文信息,在所有函數(shù)的第一個輸入?yún)?shù)中出現(xiàn),然后在你的系統(tǒng)中到處穿梭。

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

          使用演示

          此章節(jié)簡要介紹如何安裝與使用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函數(shù)啟動了一個新的協(xié)程,因此Goid()返回了主協(xié)程1AllGoids()返回了主協(xié)程及協(xié)程18:

          curr goid: 1
          all goids: [1 18]

          使用LocalStorage

          以下代碼簡單演示了LocalStorage的創(chuàng)建、設置、獲取、跨協(xié)程傳播等:

          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())

              // 其他協(xié)程不能讀取前面Set的"hello world"
              go func() {
                  fmt.Println("name1: ", nameVar.Get())
              }()

              // 但是可以通過Go函數(shù)啟動新協(xié)程,并將當前main協(xié)程的全部協(xié)程上下文變量賦值過去
              routine.Go(func() {
                  fmt.Println("name2: ", nameVar.Get())
              })

              // 或者,你也可以手動copy當前協(xié)程上下文至新協(xié)程,Go()函數(shù)的內(nèi)部實現(xiàn)也是如此
              ic := routine.BackupContext()
              go func() {
                  routine.InheritContext(ic)
                  fmt.Println("name3: ", nameVar.Get())
              }()

              time.Sleep(time.Second)
          }

          執(zhí)行結果為:

          name:  hello world
          name1:  <nil>
          name3:  hello world
          name2:  hello world

          API文檔

          此章節(jié)詳細介紹了routine庫封裝的全部接口,以及它們的核心功能、實現(xiàn)方式等。

          Goid() (id int64)

          獲取當前goroutinegoid

          在正常情況下,Goid()優(yōu)先嘗試通過go_tls的方式直接獲取,此操作性能極高,耗時通常只相當于rand.Int()的五分之一。

          若出現(xiàn)版本不兼容等錯誤時,Goid()會嘗試降級,即從runtime.Stack信息中解析獲取,此時性能會急劇下降約千倍,但它可以保證功能正常可用。

          AllGoids() (ids []int64)

          獲取當前進程全部活躍goroutinegoid

          go 1.15及更舊的版本中,AllGoids()會嘗試從runtime.Stack信息中解析獲取全部協(xié)程信息,但此操作非常低效,非常不建議在高頻邏輯中使用。

          go 1.16之后的版本中,AllGoids()會通過native的方式直接讀取runtime的全局協(xié)程池信息,在性能上得到了極大的提高, 但考慮到生產(chǎn)環(huán)境中可能有萬、百萬級的協(xié)程數(shù)量,因此仍不建議在高頻使用它。

          NewLocalStorage():

          創(chuàng)建一個新的LocalStorage實例,它的設計思路與用法和其他語言中的ThreadLocal非常相似。

          BackupContext() *ImmutableContext

          備份當前協(xié)程上下文的local storage數(shù)據(jù),它只是一個便于上下文數(shù)據(jù)傳遞的不可變結構體。

          InheritContext(ic *ImmutableContext)

          主動繼承備份到的上下文local storage數(shù)據(jù),它會將其他協(xié)程BackupContext()的數(shù)據(jù)復制入當前協(xié)程上下文中,從而支持跨協(xié)程的上下文數(shù)據(jù)傳播

          Go(f func())

          啟動一個新的協(xié)程,同時自動將當前協(xié)程的全部上下文local storage數(shù)據(jù)復制至新協(xié)程,它的內(nèi)部實現(xiàn)由BackupContext()InheritContext()組成。

          LocalStorage

          表示協(xié)程上下文變量,支持的函數(shù)包括:

          • Get() (value interface{}):獲取當前協(xié)程已設置的變量值,若未設置則為nil

          • Set(v interface{}) interface{}:設置當前協(xié)程的上下文變量值,返回之前已設置的舊值

          • Del() (v interface{}):刪除當前協(xié)程的上下文變量值,返回已刪除的舊值

          • Clear():徹底清理此上下文變量在所有協(xié)程中保存的舊值

          提示:Get/Set/Del的內(nèi)部實現(xiàn)采用無鎖設計,在大部分情況下,它的性能表現(xiàn)都應該非常穩(wěn)定且高效。

          垃圾回收

          routine庫內(nèi)部維護了全局的storages變量,它存儲了全部協(xié)程的上下文變量信息,在讀寫時基于協(xié)程的goid和協(xié)程變量的ptr進行變量尋址映射。
          在進程的整個生命周期中,它可能會創(chuàng)建于銷毀無數(shù)個協(xié)程,那么這些協(xié)程的上下文變量如何清理呢?
          為解決這個問題,routine內(nèi)部分配了一個全局的GCTimer,此定時器會在storages需要被清理時啟動,定時掃描并清理dead協(xié)程在storages中緩存的上下文變量,從而避免可能出現(xiàn)的內(nèi)存泄露隱患。

          License

          MIT


          點擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復“ 入群 ”即可加入我們的技術交流群,收獲更多的技術文章~

          - END -


          瀏覽 31
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  思思热99在线 | 手机A V在线 | 音彰先锋成人无码视频 | SM无码| 99re视频在线播放 |