最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Go如何實現協(xié)程并發(fā)執(zhí)行

 更新時間:2023年08月28日 11:13:40   作者:unitiny  
線程是通過本地隊列,全局隊列或者偷其它線程的方式來獲取協(xié)程的,目前看來,線程運行完一個協(xié)程后再從隊列中獲取下一個協(xié)程執(zhí)行,還只是順序執(zhí)行協(xié)程的,而多個線程一起這么運行也能達到并發(fā)的效果,接下來就給給大家詳細介紹一下Go如何實現協(xié)程并發(fā)執(zhí)行

順序執(zhí)行有什么問題

很明顯,順序執(zhí)行會造成協(xié)程的饑餓問題。如果某個大協(xié)程掛在線程中運行了十分鐘,那么隊列中其它協(xié)程就一直處于休眠中無法運行,這不公平。如果讓某些實時性強的協(xié)程饑餓,得不到cpu運行,會影響業(yè)務。比如視頻彈幕,用戶發(fā)出一條彈幕,得盡快顯示在視頻中。若此時協(xié)程饑餓,得不到處理,用戶體驗就差了。

該如何解決呢?簡單,讓大協(xié)程切換出去就可以了。

協(xié)程切換

回到線程循環(huán)這張圖中(在深入考究協(xié)程一文中有解釋),業(yè)務方法這塊即線程執(zhí)行的協(xié)程。如果業(yè)務方法運行時間過長,則觸發(fā)協(xié)程切換。

  • 對協(xié)程:保存該協(xié)程運行的情況,然后將該協(xié)程放入本地隊列隊尾,休眠該協(xié)程。
  • 對線程:從業(yè)務方法中跳出,重新執(zhí)行 schedule 方法,之后會從本地隊列中獲取一個新的協(xié)程運行。

image.png

但這樣只是本地隊列的協(xié)程切換,全局隊列的協(xié)程仍會饑餓,該如何解決呢?

隨機抽取全局協(xié)程

在線程循環(huán)的 shedule findRunnable 函數中,每隔一段時間就會從全局隊列中獲取一個協(xié)程放到本地隊列,再通過本地隊列的協(xié)程切換,使得來自全局隊列的協(xié)程有機會運行,從而解決全局隊列協(xié)程的饑餓問題。來看下源碼:

if pp.schedtick%61 == 0 && sched.runqsize > 0 {
   lock(&sched.lock)
   gp := globrunqget(pp, 1)
   unlock(&sched.lock)
   if gp != nil {
      return gp, false, false
   }
}

pp.schedtick 表示線程循環(huán)的次數,如果達到61的倍數,就執(zhí)行 globrunqget ,從全局隊列中獲取協(xié)程。

協(xié)程如何并發(fā)執(zhí)行

從以上可得知,線程通過切換協(xié)程的方式,不再順序的執(zhí)行協(xié)程了,從而達到并發(fā)執(zhí)行協(xié)程的效果。這關鍵在于協(xié)程的切換,那協(xié)程在什么時候會切換呢?

協(xié)程切換時機

協(xié)程的切換時機如下:

  • 主動掛起,調用 gopark 函數,使協(xié)程主動休眠等待
  • 系統(tǒng)調用完成后,io操作耗時,因此切換協(xié)程
  • 基于協(xié)作的搶占式調度,協(xié)程在跳轉到其它方法時,就把自己切換出去
  • 基于信號的搶占式調度,通過發(fā)送信號,觸發(fā)線程的調度方法

主動掛起

協(xié)程可以調用 runtime.gopark 方法,使自己陷入休眠。

image.png

源碼如下:

// 將當前協(xié)程置于等待狀態(tài)
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
   if reason != waitReasonSleep {
      checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
   }
   mp := acquirem()
   gp := mp.curg
   status := readgstatus(gp)
   if status != _Grunning && status != _Gscanrunning {
      throw("gopark: bad g status")
   }
   mp.waitlock = lock
   mp.waitunlockf = unlockf
   gp.waitreason = reason
   mp.waittraceev = traceEv
   mp.waittraceskip = traceskip
   releasem(mp)
   // can't do anything that might move the G between Ms here.
   mcall(park_m)
}

可以看到:

  1. gopark 中通過 acquirem 獲取到當前的線程指針mp
  2. 通過mp獲取到當前運行的協(xié)程指針gp
  3. 給mp,gp的一些字段賦值,修改狀態(tài)
  4. 然后調用 mcall , mcall 是一個匯編方法,作用時切換到g0棧,并執(zhí)行傳入的函數。這里執(zhí)行 park_m 函數,最終跳轉到 schedule 方法,也就是線程循環(huán)的開頭,實現了協(xié)程的主動切換。
// park_m函數最終跳轉到schedule
func park_m(gp *g) {
   mp := getg().m
   ...
   schedule()
}

由于gopark是小寫開頭的,外部無法調用。我們在使用 time.Sleep sync.WaitGroup 時,會間接的使用到gopark,將協(xié)程休眠。

系統(tǒng)調用完成后

當協(xié)程要執(zhí)行讀寫文件、網絡 IO、進程間通信等系統(tǒng)調用的操作時,會進入 entersyscall 函數,將該協(xié)程暫停并放入等待隊列。

當系統(tǒng)調用完成后,由于io操作都比較耗時,說明該協(xié)程已經運行了挺長一段時間了,因此將協(xié)程掛起,切換另一個協(xié)程執(zhí)行很合理。

image.png

exitsyscall 也位于runtime中,源碼部分如下:

func exitsyscall() {
   gp := getg()
   ...
   mcall(exitsyscall0)
   ...
}

又是熟悉的 mcall ,mcall執(zhí)行了 exitsyscall0 函數,最終跳轉到線程循環(huán)開頭的 schedule 函數,完成協(xié)程切換。

基于協(xié)作的搶占式調度

如果協(xié)程既不主動掛起,也沒有進行系統(tǒng)調用呢,那就一直切換不出去了?該怎么解決呢,如果每個協(xié)程都經常調用同一個方法的話,那就可以在這個方法里加入一個鉤子,讓這個協(xié)程切換出去。

思路有了,具體找哪個方法呢?這里做一個演示。

package main
import (
   "fmt"
   "time"
)
func do1() {
   do2()
}
func do2() {
   do3()
}
func do3() {
   fmt.Println("do3")
}
func main() {
   go do1()
   time.Sleep(time.Hour)
}

以上代碼開啟一個do1協(xié)程,do1調用do2,do2調用do3。我們通過 go build -gcflags -S main.go 命令,查看匯編代碼,發(fā)現多次調用到了 runtime.morestack_noctxt 方法。在函數跳轉的時候,編譯器會插入 runtime.morestack_noctxt 這個方法。目的是檢查函數棧空間是否足夠。 簡略源碼如下:

TEXT runtime·morestack_noctxt(SB),NOSPLIT,$0
   MOVL   $0, DX
   JMP    runtime·morestack(SB)
TEXT runtime·morestack(SB),NOSPLIT|NOFRAME,$0-0
   ...
   BL runtime·newstack(SB)
   ...

最終調用到 newstack 這個go方法。

現在對于運行時間超過10ms的大協(xié)程,其 g.stackguard0 會被賦值為 stackPreempt ,意味著該協(xié)程要切換出去了。

stackPreempt值為 0xfffffade

// 0xfffffade in hex.
const stackPreempt = uintptrMask & -1314

于是在 newstack 方法中會判斷 g.stackguard0 是否為 stackPreempt ,是則將該協(xié)程切換出去。

func newstack() {
        // 判斷是否有搶占信號
        preempt := stackguard0 == stackPreempt
        ...
	if preempt {
		...
		// Act like goroutine called runtime.Gosched.
		gopreempt_m(gp) // never return
	}
        ...
}
func gopreempt_m(gp *g) {
   ...
   goschedImpl(gp)
}
func goschedImpl(gp *g) {
   ...
   schedule()
}

以上流程總結來說:

  • Go對大協(xié)程會把g.stackguard0標記為stackPreempt。
  • 在大協(xié)程調用其它函數時,會調用newstack判斷??臻g,順便判斷該協(xié)程是否要切換出去。
  • 要切換則進入gopreempt_m -> goschedImpl -> schedule,最終回到線程循環(huán)的開頭。

流程圖如下:

image.png

基于信號的搶占式調度

如果協(xié)程不主動掛起,不系統(tǒng)調用,不調用其它函數,只是純計算的任務,那該如何切換呢?如下:

go func() {
   i := 0
   for {
      i++
   }
}()

Go就利用了操作系統(tǒng)通信的方式,通過GC的線程向該協(xié)程對應的線程發(fā)送信號,觸發(fā)該線程的切換方法。具體步驟為:

  • 注冊 SIGURG 信號的處理函數
  • GC線程工作時,向該目標線程發(fā)送信號
  • 線程接收信號后,觸發(fā)調度方法

流程圖如下:

image.png

源碼分析:

線程接收到操作系統(tǒng)信號,進入 sighandler 方法,識別信號為SIGURG,進入 doSigPreempt 方法。 之后流程:doSigPreempt -> asyncPreempt -> asyncPreempt2 -> mcall -> gopreempt_m -> goschedImpl。 最終調用schedule方法,回到線程開頭,完成協(xié)程切換。

具體細節(jié)各位可以動手查看下,感悟更多。

總結

要使協(xié)程并發(fā)執(zhí)行,那各個線程就不能順序的執(zhí)行協(xié)程,得選擇合適的時機將協(xié)程切換出去,換另一個協(xié)程執(zhí)行。因此切換時機就特別重要了,所以本篇重點講解了四種切換方式,分別為:

  • 協(xié)程主動掛起,調用 gopark 函數,使協(xié)程主動休眠等待
  • 系統(tǒng)調用完成后,由于io操作挺耗時,代表該協(xié)程運行太久了,因此切換協(xié)程
  • 基于協(xié)作的搶占式調度,協(xié)程運行超10ms,就標記為搶占。這時協(xié)程在跳轉到其它方法時,就把自己切換出去
  • 基于信號的搶占式調度,協(xié)程純自閉,得外部干擾。因此通過GC線程發(fā)送信號,觸發(fā)線程的調度方法

以上就是詳解Go如何實現協(xié)程并發(fā)執(zhí)行的詳細內容,更多關于Go協(xié)程并發(fā)執(zhí)行的資料請關注腳本之家其它相關文章!

相關文章

  • Gotify搭建你的消息推送系統(tǒng)

    Gotify搭建你的消息推送系統(tǒng)

    這篇文章主要介紹了Gotify搭建你的消息推送系統(tǒng),今天要分享的是 gotify,是一個用 go 編寫的消息服務端,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2024-01-01
  • Go讀取配置文件的方法總結

    Go讀取配置文件的方法總結

    我們常見的配置文件的格式一般有:XML、JSON、INI、YAML、env和.properties,本文小編為大家整理了Go語言讀取這些格式的配置文件的方法,希望對大家有所幫助
    2023-10-10
  • go HTTP2 的頭部壓縮算法hpack實現詳解

    go HTTP2 的頭部壓縮算法hpack實現詳解

    這篇文章主要為大家介紹了go HTTP2 的頭部壓縮算法hpack實現詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • golang實現aes-cbc-256加密解密功能

    golang實現aes-cbc-256加密解密功能

    這篇文章主要介紹了golang實現aes-cbc-256加密解密功能,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • 淺析Go語言中包的介紹與初始化

    淺析Go語言中包的介紹與初始化

    這篇文章主要為大家詳細介紹了Go語言中包的介紹與初始化,從而搞清Go程序的執(zhí)行次序,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起了解下
    2023-10-10
  • 深入理解golang chan的使用

    深入理解golang chan的使用

    本文主要介紹了golang chan的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-06-06
  • Golang map如何生成有序的json數據詳解

    Golang map如何生成有序的json數據詳解

    最近在學習Golang,發(fā)現了一個問題,覺著有必要給大家總結下,下面這篇文章主要給大家介紹了關于Golang map如何生成有序json數據的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友們下面來一起看看吧。
    2017-07-07
  • golang?debug調試的實現

    golang?debug調試的實現

    本文主要介紹了使用Go語言進行本地調試和遠程調試,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-12-12
  • gRPC中攔截器的使用詳解

    gRPC中攔截器的使用詳解

    這篇文章主要介紹了gRPC中攔截器的使用詳解,本次主要介紹在gRPC中使用攔截器,包括一元攔截器和流式攔截器,在攔截器中添加JWT認證,客戶端登錄之后會獲得token,請求特定的API時候需要帶上token才能訪問,需要的朋友可以參考下
    2023-10-10
  • golang簡單獲取上傳文件大小的實現代碼

    golang簡單獲取上傳文件大小的實現代碼

    這篇文章主要介紹了golang簡單獲取上傳文件大小的方法,涉及Go語言文件傳輸及文件屬性操作的相關技巧,需要的朋友可以參考下
    2016-07-07

最新評論

新营市| 保定市| 二连浩特市| 新丰县| 绵竹市| 漯河市| 读书| 禹州市| 巧家县| 水富县| 福泉市| 运城市| 商城县| 涪陵区| 石屏县| 科技| 庆城县| 鹤山市| 开封市| 浙江省| 广灵县| 盐津县| 武夷山市| 当涂县| 宜川县| 和静县| 昌宁县| 永兴县| 衢州市| 新平| 德昌县| 龙州县| 天长市| 福泉市| 论坛| 高安市| 伊春市| 聊城市| 石嘴山市| 喀喇| 南康市|