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

          「Go實(shí)戰(zhàn)系列」迷惑的 goroutine 執(zhí)行順序

          共 3632字,需瀏覽 8分鐘

           ·

          2021-09-02 20:02

          這個(gè)系列會(huì)講一些從課程中學(xué)到的讓人醍醐灌頂?shù)臇|西,撥云見日,帶你重新認(rèn)識(shí) Go。

          上一篇文章我們講了 Go 調(diào)度的本質(zhì)是一個(gè)生產(chǎn)-消費(fèi)流程。

          生產(chǎn)端是正在運(yùn)行的 goroutine 執(zhí)行 go func(){}() 語(yǔ)句生產(chǎn)出 goroutine 并塞到三級(jí)隊(duì)列中去。

          消費(fèi)端則是 Go 進(jìn)程中的 m 在不斷地執(zhí)行調(diào)度循環(huán),從三級(jí)隊(duì)列中拿到 goroutine 來(lái)運(yùn)行。

          生產(chǎn)-消費(fèi)過(guò)程

          今天我們來(lái)通過(guò) 2 個(gè)實(shí)際的代碼例子來(lái)看看 goroutine 的執(zhí)行順序是怎樣的。

          第一個(gè)例子

          首先來(lái)看第一個(gè)例子:

          package main

          import (
           "fmt"
           "runtime"
           "time"
          )

          func main() {
              runtime.GOMAXPROCS(1)
              for i := 0; i < 10; i++ {
                  i := i
                  go func() {
                      fmt.Println(i)
                  }()
              }

              var ch = make(chan int)
              <- ch
          }

          首先通過(guò) runtime.GOMAXPROCS(1) 設(shè)置只有一個(gè) P,接著創(chuàng)建了 10 個(gè) goroutine,并分別打印出 i 值。你可以先想一下輸出會(huì)是什么,再對(duì)著答案會(huì)有更深入的理解。

          揭曉答案:

          9
          0
          1
          2
          3
          4
          5
          6
          7
          8
          fatal error: all goroutines are asleep - deadlock!

          goroutine 1 [chan receive]:
          main.main()
                  /home/raoquancheng/go/src/hello/main.go:16 +0x96
          exit status 2

          程序輸出的 fatal error 是因?yàn)?main goroutine 正在從一個(gè) channel 里讀數(shù)據(jù),而這時(shí)所有的 channel 都已經(jīng)掛了,因此出現(xiàn)死鎖。這里先忽略這個(gè),只需要關(guān)注 i 輸出的順序:9, 0, 1, 2, 3, 4, 5, 6, 7, 8

          我來(lái)解釋一下原因:因?yàn)橐婚_始就設(shè)置了只有一個(gè) P,所以 for 循環(huán)里面“生產(chǎn)”出來(lái)的 goroutine 都會(huì)進(jìn)入到 P 的 runnext 和本地隊(duì)列,而不會(huì)涉及到全局隊(duì)列。

          每次生產(chǎn)出來(lái)的 goroutine 都會(huì)第一時(shí)間塞到 runnext,而 i 從 1 開始,runnext 已經(jīng)有 goroutine 在了,所以這時(shí)會(huì)把 old goroutine 移動(dòng) P 的本隊(duì)隊(duì)列中去,再把 new goroutine 放到 runnext。之后會(huì)重復(fù)這個(gè)過(guò)程……

          因此這后當(dāng)一次 i 為 9 時(shí),新 goroutine 被塞到 runnext,其余 goroutine 都在本地隊(duì)列。

          之后,main goroutine 執(zhí)行了一個(gè)讀 channel 的語(yǔ)句,這是一個(gè)好的調(diào)度時(shí)機(jī):main goroutine 掛起,運(yùn)行 P 的 runnext 和本地可運(yùn)行隊(duì)列里的 gorotuine。

          而我們又知道,runnext 里的 goroutine 的執(zhí)行優(yōu)先級(jí)是最高的,因此會(huì)先打印出 9,接著再執(zhí)行本地隊(duì)列中的 goroutine 時(shí),按照先進(jìn)先出的順序打印:0, 1, 2, 3, 4, 5, 6, 7, 8

          是不是非常有意思?

          第二個(gè)例子

          別急,我們?cè)賮?lái)看第 2 個(gè)例子:

          package main

          import (
           "fmt"
           "runtime"
           "time"
          )

          func main() {
              runtime.GOMAXPROCS(1)
              for i := 0; i < 10; i++ {
                  i := i
                  go func() {
                      fmt.Println(i)
                  }()
              }

              time.Sleep(time.Hour)
          }

          和第一個(gè)例子的不同之處是我們把讀 channel 的代碼換成 Sleep 操作。這一次,你還能正確回答 i 的輸出順序是什么嗎?

          我們直接揭曉答案。

          當(dāng)我們用 go1.13 運(yùn)行時(shí):

          $ go1.13.8 run main.go

          0
          1
          2
          3
          4
          5
          6
          7
          8

          而當(dāng)我們用 go1.14 及之后的版本運(yùn)行時(shí):

          $ go1.14 run main.go

          9
          0
          1
          2
          3
          4
          5
          6
          7
          8

          可以看到,用 go1.14 及之后的版本運(yùn)行時(shí),輸出順序和之前的一致。而用 go1.13 運(yùn)行時(shí),卻先輸出了 0,這又是什么原因呢?

          這就要從 Go 1.14 修改了 timer 的實(shí)現(xiàn)開始說(shuō)起了。

          go 1.13 的 time 包會(huì)生產(chǎn)一個(gè)名字叫 timerproc 的 goroutine 出來(lái),它專門用于喚醒掛在 timer 上的時(shí)間未到期的 goroutine;因此這個(gè) goroutine 會(huì)把 runnext 上的 goroutine 擠出去。因此輸出順序就是:0, 1, 2, 3, 4, 5, 6, 7, 8, 9

          而 go 1.14 把這個(gè)喚醒的 goroutine 干掉了,取而代之的是,在調(diào)度循環(huán)的各個(gè)地方、sysmon 里都是喚醒 timer 的代碼,timer 的喚醒更及時(shí)了,但代碼也更難看懂了。所以,輸出順序和第一個(gè)例子是一致的。

          總結(jié)

          今天通過(guò) 2 個(gè)實(shí)際的例子再次復(fù)習(xí)了 Go 調(diào)度消費(fèi)端的流程,也學(xué)到了 time 包在不同 go 版本下的不同之處以及它對(duì)程序輸出造成的影響。

          有些人還會(huì)把例子中的 10 改成比 256 更大的數(shù)去嘗試。曹大說(shuō)這是考眼力,不要給自己找事。因?yàn)檫@時(shí) P 的本地隊(duì)列裝不下這么多 goroutine 了,只能放到全局隊(duì)列。這下程序的輸出順序就不那么直觀了。

          所以,記住本文的核心內(nèi)容就行了:

          1. runnext 的優(yōu)先級(jí)最高。
          2. time.Sleep 在老版本中會(huì)創(chuàng)建一個(gè) goroutine,在 1.14(包含)之后不會(huì)創(chuàng)建 goroutine 了。

          如果被別人考到,知道三級(jí)隊(duì)列,以及 time 包在 1.14 的變更就行了。

          想要獲取了解更多 「Go 實(shí)戰(zhàn)」的相關(guān)信息,趕緊掃碼進(jìn)群哦~


          如果群滿,可加小助手,就能拉你一起加入群聊啦






          瀏覽 61
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  大尺度在线 | 大陆三级视频在线观看 | 色精品 | 精品av在线观看 精品久久中文字幕 | 日本无码 视频在线观看 |