go并發(fā)控制利器-centext(鐵索連環(huán))

      網(wǎng)友投稿 805 2022-05-30

      更便捷的goroutine控制利器- Context

      首先要和大家說聲抱歉哈,由于工作上、生活上的某些瑣事,以至于造成本節(jié)的斷更。不過請不要悲傷。因為我在這期間也是做過詳細(xì)的復(fù)習(xí)的。我相信一定會讓你有更加深入的理解,同時也歡迎你向我提出不足。我們共同進步。話不多說,我相信你已經(jīng)迫不及待了。還在等什么?let‘s GO

      在本文中,我首先會介紹context是什么,它有什么作用,以及如何使用,其中還會參雜一點個人的理解,以及部分源碼的了解。What are you waiting for?

      Context:

      來自官方文檔

      Context包定義了上下文類型,該類型在API邊界之間以及進程之間傳遞截止日期,取消信號和其他請求范圍的值

      對服務(wù)器的傳入請求應(yīng)創(chuàng)建一個Context,而對服務(wù)器的傳出調(diào)用應(yīng)接受一個Context。

      它們之間的函數(shù)調(diào)用鏈必須傳播Context,可以選擇將其替換為使用WithCancel,WithDeadline,WithTimeout或WithValue創(chuàng)建的派生Context。取消上下文后,從該上下文派生的所有上下文也會被取消。

      WithCancel,WithDeadline和WithTimeout函數(shù)采用Context(父級)并返回派生的Context(子級)和CancelFunc。調(diào)用CancelFunc會取消該子代及其子代,刪除父代對該子代的引用,并停止所有關(guān)聯(lián)的計時器。未能調(diào)用CancelFunc會使子代及其子代泄漏,直到父代被取消或計時器觸發(fā)。審核工具檢查所有控制流路徑上是否都使用了CancelFuncs。

      使用上下文的程序應(yīng)遵循以下規(guī)則,以使各個包之間的接口保持一致,并使靜態(tài)分析工具可以檢查上下文傳播:

      不要將上下文存儲在結(jié)構(gòu)類型中;而是將上下文明確傳遞給需要它的每個函數(shù)。 Context應(yīng)該是第一個參數(shù),通常命名為ctx:

      func?DoSomething(ctx?context.Context,?arg?Arg)?error?{

      //??...?use?ctx?...

      }

      即使函數(shù)允許,也不要傳遞nil Context。如果不確定使用哪個上下文,請傳遞context.TODO

      僅將上下文值用于傳遞過程和API的請求范圍數(shù)據(jù),而不用于將可選參數(shù)傳遞給函數(shù)。

      可以將相同的上下文傳遞給在不同goroutine中運行的函數(shù)。上下文可以安全地被多個goroutine同時使用

      巴拉巴拉,說了一大堆,反正我一句沒懂,當(dāng)然我知道context是干嘛的,(尬~,不小心暴露了,學(xué)渣的本質(zhì)),說說我的理解以及使用建議

      對服務(wù)器的傳入請求應(yīng)創(chuàng)建一個Context,而對服務(wù)器的傳出響應(yīng)也應(yīng)接受一個Context。

      函數(shù)調(diào)用鏈必須傳播Context,也可以選擇將其替換為使用WithCancel,WithDeadline,WithTimeout或WithValue創(chuàng)建的派生Context(也就是子類context)。取消上下文后,從該上下文派生的所有上下文也會被取消

      Context 不要放在結(jié)構(gòu)體中,要以參數(shù)的方式傳遞。

      Context 作為函數(shù)的參數(shù)時,要放在第一位,也就是第一個參數(shù)。

      要使用 context.Background 函數(shù)生成根節(jié)點的 Context,也就是最頂層的 Context。

      Context 傳值要傳遞必須的值,而且要盡可能地少,不要什么都傳。

      Context 多協(xié)程安全,可以在多個協(xié)程中放心使用。

      go Context定義

      Context 是Go 1.7 標(biāo)準(zhǔn)庫引入 的標(biāo)準(zhǔn)庫,中文譯作“上下文”,準(zhǔn)確說它是 goroutine 的上下文,包含 goroutine 的運行狀態(tài)、環(huán)境、現(xiàn)場等信息。

      使用context,我們可以輕松優(yōu)雅的做到取消goroutine,超時時間,運行截止時間,k-v存儲等。它是并發(fā)安全的

      隨著 context 包的引入,標(biāo)準(zhǔn)庫中很多接口因此加上了 context 參數(shù),例如 database/sql 包。context 幾乎成為了并發(fā)控制和超時控制的標(biāo)準(zhǔn)做法。

      context.Context 類型的值可以協(xié)調(diào)多個 groutine 中的代碼執(zhí)行“取消”操作,并且可以存儲鍵值對。最重要的是它是并發(fā)安全的。

      與它協(xié)作的 API 都可以由外部控制執(zhí)行“取消”操作,例如:取消一個 HTTP 請求的執(zhí)行。

      止于這些么?當(dāng)然 不止,還有更多的騷操作,接下來讓我們一起拿下它吧。

      引入

      為什么需要使用context,理由一

      一個協(xié)程啟動后,大部分情況需要等待里面的代碼執(zhí)行完畢,然后協(xié)程會自行退出。但需要讓協(xié)程提前退出怎么辦呢?

      下面我們以一個小的示例,來逐漸了解context的妙用之一吧

      package?main

      import?(

      "fmt"

      "sync"

      "time"

      )

      func?monitor(name?string)?{

      //開啟for?select循環(huán),j進行后臺監(jiān)控

      for?{

      select?{

      default:

      fmt.Printf("Time:?%v?監(jiān)控者:%s,?正在監(jiān)控...\n",?time.Now().Unix(),?name)

      }

      //?sleep?1?second

      time.Sleep(time.Second?*?5)

      fmt.Printf("%s?監(jiān)控完成,一切正常,請指示?over...\n",?name)

      }

      }

      func?main()?{

      var?wg?sync.WaitGroup

      //?Define?waiting?group

      wg.Add(1)

      go?func()?{

      //?Execution?complete

      defer?wg.Done()

      monitor("天眼")

      }()

      //Exit?after?waiting

      wg.Wait()

      }

      我們在這里實現(xiàn)了一個基本的groutine執(zhí)行的case

      我們定義了等待組wait group,防止協(xié)程提前退出。關(guān)于wait group可參考上一篇文章,golang并發(fā)控制的心應(yīng)手。

      他會周期性的運行,不斷打印監(jiān)控信息,例如

      那么我們完成上述的那個需求提前退出,那么該怎么辦呢?其中一個方法就是定義一個全局的sign,其他地方可以通過修改這個sign發(fā)出停止監(jiān)控的指令。然后在協(xié)程中先檢查這個變量,如果發(fā)現(xiàn)被通知關(guān)閉就停止監(jiān)控,退出當(dāng)前協(xié)程。從而實現(xiàn)可控制提前退出。示例代碼如下

      package?main

      import?(

      "fmt"

      "sync"

      "time"

      )

      func?monitor1(signCh?chan?bool,?MonitoringPeriod?time.Duration,?name?string,?)?{

      //開啟for?select循環(huán),一直后臺監(jiān)控

      for?{

      select?{

      case?<-signCh:

      fmt.Println(name,?"停止指令已收到,停止...")

      return

      default:

      fmt.Printf("Time:?%v?[監(jiān)控者]:%s,?正在監(jiān)控...\n",?time.Now().Unix(),?name)

      }

      time.Sleep(MonitoringPeriod?*?time.Second)

      }

      }

      func?main()?{

      var?wg?sync.WaitGroup

      signCh?:=?make(chan?bool)?//sign?用來停止監(jiān)控

      const?MonitoringTime,?MonitoringPeriod?=?20,?2

      wg.Add(1)

      go?func()?{

      defer?wg.Done()

      monitor1(signCh,?MonitoringPeriod,?"天眼")

      }()

      time.Sleep(MonitoringTime?*?time.Second)?//實施監(jiān)控時間

      signCh?<-?true???????????????????????????//發(fā)停止指令

      wg.Wait()

      }

      這樣我們就實現(xiàn)了,可控制話的groutine退出,但如果在新增幾個定期的任務(wù)功能,那該如何是好?

      管他的,我們先把這個弄懂了先。老夫先干為敬。首先我們先看程序運行圖,如下

      這個示例是使用 select+channel 的方式改造,實現(xiàn)了通過 channel 發(fā)送指令讓監(jiān)控狗停止,進而達到協(xié)程退出的目的。

      首先我們定義了sync.WaitGroup,防止gorontine提前退出。signCh,他是一個bool值類型channel,用于發(fā)送sign后續(xù)的退出。

      MonitoringTime,MonitoringPeriod,監(jiān)控時間與監(jiān)控周期。second。

      然后創(chuàng)建goroutine執(zhí)行select+channel。

      Go Context 初試體驗

      為 函數(shù)增加 signCh 參數(shù),用于接收停止指令;

      在 main 函數(shù)中,聲明用于停止的 signCh,傳遞給 monitor1 函數(shù),然后通過 signCh<-true 發(fā)送停止指令讓協(xié)程退出。

      通過 select+channel 讓協(xié)程退出的方式比較優(yōu)雅,以下幾個問題也隨之凸顯

      但如果我們希望做到同時取消很多個協(xié)程呢?

      如果是定時取消協(xié)程又該怎么辦?

      這時候 select+channel 的局限性就凸現(xiàn)出來了,即使定義了多個 channel 解決問題,當(dāng)然這個方式是可行的,但代碼邏輯也會非常復(fù)雜、難以維護。

      要解決這種復(fù)雜的協(xié)程問題,必須有一種可以跟蹤協(xié)程的方案,只有跟蹤到每個協(xié)程,才能更好地控制它們,這種方案就是 Go 語言標(biāo)準(zhǔn)庫為我們提供的 Context,接下來我們體驗一下它的強大之處吧。

      package?main

      import?(

      "context"

      "fmt"

      "sync"

      "time"

      )

      func?main()?{

      var?wg?sync.WaitGroup

      const?MonitoringTime,?MonitoringPeriod?=?20,?2

      wg.Add(1)

      //?定義一個等待的?`context`

      ctx,?stop?:=?context.WithCancel(context.Background())

      go?func()?{

      defer?wg.Done()

      monitor2(ctx,?MonitoringPeriod,?"天眼")

      }()

      time.Sleep(MonitoringTime?*?time.Second)?//先監(jiān)控5秒

      stop()???????????????????????????????????//發(fā)停止指令

      wg.Wait()

      }

      func?monitor2(ctx?context.Context,?MonitoringPeriod?time.Duration,?name?string,?)?{

      //開啟for?select循環(huán),一直后臺監(jiān)控

      for?{

      select?{

      case?<-ctx.Done():

      fmt.Println(name,?"停止指令已收到,停止...")

      return

      default:

      fmt.Printf("Time:?%v?[監(jiān)控者]:%s,?正在監(jiān)控...\n",?time.Now().Unix(),?name)

      }

      time.Sleep(MonitoringPeriod?*?time.Second)

      }

      }

      是不是很優(yōu)雅呢?確實如此,那么為什么也可以達到上面使用channel,的效果呢。那么我們?nèi)タ匆幌滤木唧w實現(xiàn)部分呢,

      `WithCancel`

      以下是WithCancel:具體實現(xiàn)部分代碼

      WithCancel:返回具有新的“完成”通道的父級副本。當(dāng)調(diào)用返回的cancel函數(shù)或關(guān)閉父上下文的Done通道時(以先發(fā)生的為準(zhǔn)),將關(guān)閉返回的上下文的Done通道。取消此上下文將釋放與其關(guān)聯(lián)的資源,因此在此上下文中運行的操作完成后,代碼應(yīng)立即調(diào)用cancel。

      func?WithCancel(parent?Context)?(ctx?Context,?cancel?CancelFunc)?{

      if?parent?==?nil?{

      panic("cannot?create?context?from?nil?parent")

      }

      c?:=?newCancelCtx(parent)

      propagateCancel(parent,?&c)

      return?&c,?func()?{?c.cancel(true,?Canceled)?}

      }

      除了WithCancel,之外還有WithDeadline,WithTimeout,WithValue,首先我們來繼續(xù)看看WithDeadline具體實現(xiàn),以及使用技巧吧

      `WithTimeout`

      func?WithDeadline(parent?Context,?d?time.Time)?(Context,?CancelFunc)?{

      if?parent?==?nil?{

      panic("cannot?create?context?from?nil?parent")

      }

      if?cur,?ok?:=?parent.Deadline();?ok?&&?cur.Before(d)?{

      //?The?current?deadline?is?already?sooner?than?the?new?one.

      return?WithCancel(parent)

      }

      c?:=?&timerCtx{

      cancelCtx:?newCancelCtx(parent),

      deadline:??d,

      }

      propagateCancel(parent,?c)

      dur?:=?time.Until(d)

      if?dur?<=?0?{

      c.cancel(true,?DeadlineExceeded)?//?deadline?has?already?passed

      return?c,?func()?{?c.cancel(false,?Canceled)?}

      }

      c.mu.Lock()

      defer?c.mu.Unlock()

      if?c.err?==?nil?{

      c.timer?=?time.AfterFunc(dur,?func()?{

      c.cancel(true,?DeadlineExceeded)

      })

      }

      return?c,?func()?{?c.cancel(true,?Canceled)?}

      }

      func?WithTimeout(parent?Context,?timeout?time.Duration)?(Context,?CancelFunc)?{

      return?WithDeadline(parent,?time.Now().Add(timeout))

      }

      取消此上下文將釋放與之關(guān)聯(lián)的資源,因此在此上下文中運行的操作完成后,代碼應(yīng)立即調(diào)用cancel:

      來看一下具體如何使用吧,示例如下

      package?main

      import?(

      "context"

      "fmt"

      "time"

      )

      func?main()?{

      //?創(chuàng)建一個子節(jié)點的context,3秒后自動超時

      const?MonitoringTime,?MonitoringPeriod?=?20,?2

      ctx,?cancel?:=?context.WithTimeout(context.Background(),?time.Second*10)

      go?func()?{

      monitor4(ctx,?MonitoringPeriod,?"天眼")

      fmt.Println("退出時間",time.Now().Unix())

      }()

      time.Sleep(MonitoringTime?*?time.Second)

      cancel()

      }

      func?monitor4(ctx?context.Context,?MonitoringPeriod?time.Duration,?name?string,?)?{

      //開啟for?select循環(huán),一直后臺監(jiān)控

      for?{

      select?{

      case?<-ctx.Done():

      fmt.Println(name,?"停止指令已收到,停止...")

      return

      default:

      fmt.Printf("Time:?%v?[監(jiān)控者]:%s,?正在監(jiān)控...\n",?time.Now().Unix(),?name)

      time.Sleep(MonitoringPeriod?*?time.Second)

      }

      }

      }

      以上會有兩種情況發(fā)生退出,

      一、程序main退出,全局退出

      二、我們定義的timeout退出

      他們的基本性質(zhì)與使用我們就簡單的過了一遍,下面讓我們來個小結(jié)。

      WithCancel(parent Context):生成一個可取消的 Context。

      WithDeadline(parent Context, d time.Time):生成一個可定時取消的 Context,參數(shù) d 為定時取消的具體時間。

      WithTimeout(parent Context, timeout time.Duration):生成一個可超時取消的 Context,參數(shù) timeout 用于設(shè)置多久后取消

      WithValue(parent Context, key, val interface{}):生成一個可攜帶 key-value 鍵值對的 Context。

      是不是發(fā)現(xiàn),其實也沒有那么難呢?當(dāng)然,它本來就很簡單,接下來我們來點更刺激的,同時取消多goroutine,啥也不說了,上~

      package?main

      import?(

      "context"

      "fmt"

      "strconv"

      "sync"

      "time"

      )

      go并發(fā)控制利器-centext(鐵索連環(huán))

      var?wg?sync.WaitGroup

      func?main()?{

      const?MonitoringTime,?MonitoringPeriod?=?20,?2

      wg.Add(1)

      ctx,?stop?:=?context.WithCancel(context.Background())

      for?i?:=?0;?i?

      go?monitor6(ctx,?MonitoringPeriod,?strconv.Itoa(i))

      }

      time.Sleep(MonitoringTime?*?time.Second)?//先監(jiān)控5秒

      stop()???????????????????????????????????//發(fā)停止指令

      wg.Wait()

      }

      func?monitor6(ctx?context.Context,?MonitoringPeriod?time.Duration,?name?string,?)?{

      defer?wg.Done()

      //開啟for?select循環(huán),一直后臺監(jiān)控

      for?{

      select?{

      case?<-ctx.Done():

      fmt.Println(name,?"停止指令已收到,停止...")

      return

      default:

      fmt.Printf("Time:?%v?[監(jiān)控者]:天眼%s,?正在監(jiān)控...\n",?time.Now().Unix(),?name)

      }

      time.Sleep(MonitoringPeriod?*?time.Second)

      }

      }

      Time: 1612948086 [監(jiān)控者]:天眼0, 正在監(jiān)控…

      Time: 1612948086 [監(jiān)控者]:天眼1, 正在監(jiān)控…

      Time: 1612948086 [監(jiān)控者]:天眼2, 正在監(jiān)控…

      … …

      Time: 1612948104 [監(jiān)控者]:天眼2, 正在監(jiān)控…

      Time: 1612948104 [監(jiān)控者]:天眼0, 正在監(jiān)控…

      Time: 1612948104 [監(jiān)控者]:天眼1, 正在監(jiān)控…

      2 停止指令已收到,停止…

      1 停止指令已收到,停止…

      0 停止指令已收到,停止…

      你以為這樣就完了么,這只是一個小的case,它還可以管理子節(jié)點。其管理與樹形結(jié)構(gòu)十分的相似。

      除此之外還可以傳遞值,接下來讓我們來看看吧

      package?main

      import?(

      "context"

      "fmt"

      "sync"

      "time"

      )

      func?main()?{

      var?wg?sync.WaitGroup

      ctx,?stop?:=?context.WithCancel(context.Background())

      ctxVal?:=?context.WithValue(ctx,?"user",?"payne")

      wg.Add(1)

      go?func()?{

      defer?wg.Done()

      getValue(ctxVal)

      }()

      time.Sleep(3)

      stop()

      wg.Wait()

      }

      func?getValue(ctx?context.Context)?{

      for?{

      select?{

      case?<-ctx.Done():

      fmt.Println("exit")

      return

      default:

      user?:=?ctx.Value("user")

      fmt.Println("【獲取用戶】",?"用戶為:",?user)

      time.Sleep(1?*?time.Second)

      }

      }

      }

      輸出如下

      【獲取用戶】 用戶為: payne

      exit…

      總結(jié):

      Context為我們主要定義四種方法WithDeadline,WithTimeout,WithValue,WithCancel,從而達到控制goroutine的目的,但卻不僅限于我們以上介紹的那樣(只介紹了一層,其實可以是多層。形成多對多的關(guān)系),它更深層次的使用你可以想象成多叉樹的情況。

      context,這一篇就暫且完成啦,期待下一篇。并發(fā)模式

      并發(fā)模式,故名思義。他與設(shè)計模式一樣,即使用goroutine并發(fā)的一些總結(jié)。

      我將與你探討

      Goroutine WorkPool:讓我們隨影所欲的控制創(chuàng)建gototine的數(shù)量,且復(fù)用。

      Pipeline 模式,他像工廠流水線一般,我們將是這將其拆分歸并

      扇出扇入模式,在pipline的基礎(chǔ)上對耗時較長的進行處理

      Futures 模式,Pipeline 流水線模式中的工序是相互依賴的,但是在我們的實際需求中,也有大量的任務(wù)之間相互獨立、沒有依賴,所以為了提高性能,這些獨立的任務(wù)就可以并發(fā)執(zhí)行。

      期待~

      API Go

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:excel表格數(shù)據(jù)處理的方法(excel表格數(shù)據(jù)處理技巧)
      下一篇:10種方法緩解所顯示器造成的眼睛疲勞(看屏幕眼睛疲勞 怎么緩解)
      相關(guān)文章
      亚洲精品网站在线观看不卡无广告| 亚洲看片无码在线视频| 亚洲AV无码资源在线观看| 亚洲人成综合在线播放| 亚洲成色WWW久久网站| 亚洲精品成人片在线播放| 亚洲理论电影在线观看| 亚洲人成图片小说网站| 中文字幕亚洲一区二区三区| 国产亚洲一区区二区在线 | 亚洲欧洲国产综合| 亚洲欧洲国产综合| 亚洲AV无码专区在线亚| 亚洲综合中文字幕无线码| 久久国产亚洲精品| 亚洲码欧美码一区二区三区| 亚洲成a∨人片在无码2023 | 亚洲国产精品无码久久| 亚洲精品宾馆在线精品酒店| 久久亚洲精品11p| 亚洲?v无码国产在丝袜线观看| 亚洲AⅤ无码一区二区三区在线| 亚洲第一区精品观看| 亚洲精品一级无码鲁丝片| 久久久久亚洲AV成人网人人网站| 亚洲三区在线观看无套内射| 亚洲AV无码久久| 久久亚洲AV成人无码| 亚洲剧场午夜在线观看| 亚洲色成人网站WWW永久四虎 | 无码国产亚洲日韩国精品视频一区二区三区 | 怡红院亚洲红怡院在线观看| www亚洲一级视频com| 久久久久亚洲AV成人网| 亚洲AV永久精品爱情岛论坛| 亚洲国产精品久久久久婷婷老年| 亚洲日产2021三区| 亚洲日本天堂在线| 亚洲精品NV久久久久久久久久| 亚洲熟妇丰满多毛XXXX| 亚洲黄色一级毛片|