在go語言中,協程(goroutine)是指在後臺中執行的輕量級執行執行緒;go協程是Go中實現並行的關鍵組成部分。Go中提供了一個關鍵字go來建立一個Go協程,當在函數或方法的呼叫之前新增一個關鍵字go,這樣就開啟了一個Go協程,該函數或者方法就會在這個Go協程中執行。
php入門到就業線上直播課:進入學習
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:
本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。
Go 協程 (goroutine) 是指在後臺中執行的輕量級執行執行緒,go 協程是 Go 中實現並行的關鍵組成部分。
由於 Go 協程相對於傳統作業系統中的執行緒 (thread) 是非常輕量級的,因此對於一個典型的 Go 應用來說,有數以千計的 Go 協程並行執行的情形是十分常見的。並行可以顯著地提升應用的執行速度,並且可以幫助我們編寫關注點分離(Separation of concerns,Soc)的程式碼。
我們也許在理論上已經知曉 Go 協程是如何工作的,但是在程式碼層級上,go 協程何許物也?其實,go 協程看起來只是一個與其他眾 Go 協程並行執行的一個簡單函數或者方法,但是我們並不能想當然地從函數或者方法中的定義來確定一個 Go 協程,go 協程的確定還是要取決於我們如何去呼叫。【相關推薦:Go視訊教學、】
Go 中提供了一個關鍵字 go
來讓我們建立一個 Go 協程,當我們在函數或方法的呼叫之前新增一個關鍵字 go
,這樣我們就開啟了一個 Go 協程,該函數或者方法就會在這個 Go 協程中執行。
舉個簡單的栗子:
在上面的程式碼中,我們定義了一個可以在控制檯輸出 Hello World
字串的 printHello
的函數,在 main
函數中,我們就像平時那樣呼叫 printHello
函數,最終也是理所當然地獲得了期望的結果。
下面,讓我們嘗試從同一個函數建立 Go 協程:
根據 Go 協程的語法,我們在函數呼叫的前面增加了一個 go
關鍵字,之後程式執行正常,輸出了以下的結果:
main execution started
main execution stopped
登入後複製
登入後複製
奇怪的是,Hello World
並沒有如同我們預料的那樣輸出,這期間究竟發生了什麼?
go 協程總是在後臺執行,當一個 Go 協程執行的時候(在這個例子中是 go printHello()
), Go 並不會像在之前的那個例子中在執行 printHello
中的功能時阻塞 main 函數中剩下語句的執行,而是直接忽略了 Go 協程的返回並繼續執行 main 函數剩下的語句。即便如此,我們為什麼沒法看到函數的輸出呢?
在預設情況下,每個獨立的 Go 應用執行時就建立了一個 Go 協程,其 main
函數就在這個 Go 協程中執行,這個 Go 協程就被稱為 go 主協程(main Goroutine,下面簡稱主協程)
。在上面的例子中,主協程
中又產生了一個 printHello
這個函數的 Go 協程,我們暫且叫它 printHello 協程
吧,因而我們在執行上面的程式的時候,就會存在兩個 Go 協程(main
和 printHello
)同時執行。正如同以前的程式那樣,go 協程們會進行協同排程。因此,當 主協程
執行的時候,Go 排程器在 主協程
執行完之前並不會將控制權移交給 printHello 協程
。不幸的是,一旦 主協程
執行完畢,整個程式會立即終止,排程器再也沒有時間留給 printHello 協程
去執行了。
但正如我們從其他課程所知,通過阻塞條件,我們可以手動將控制權轉移給其他的 Go 協程 , 也可以說是告訴排程器讓它去排程其他可用空閒的 Go 協程。讓我們呼叫 time.Sleep()
函數去實現它吧。
如上圖所示,我們修改了程式,程式在 main 函數的最後一條語句之前呼叫了 time.Sleep(10 * time.Millisecond)
,使得 主協程
在執行最後一條指令之前排程器就將控制權轉移給了 printhello 協程
。在這個例子中,我們通過呼叫 time.Sleep(10 * time.Millisecond)
強行讓 主協程
休眠 10ms 並且在在這個 10ms 內不會再被排程器重新排程執行。
一旦 printHello 協程
執行,它就會向控制檯列印‘ Hello World !’,然後該 Go 協程(printHello 協程
)就會隨之終止,接下來 主協程
就會被重新排程(因為 main Go 協程已經睡夠 10ms 了),並執行最後一條語句。因此,執行上面的程式就會得到以下的輸出 :
main execution started
Hello World!
main execution stopped
登入後複製
登入後複製
下面我稍微修改一下例子,我在 printHello
函數的輸出語句之前新增了一條 time.Sleep(time.Millisecond)
。我們已經知道了如果我們在函數中呼叫了休眠(sleep)函數,這個函數就會告訴 Go 排程器去排程其他可被排程的 Go 協程。在上一課中提到,只有非休眠(non-sleeping
)的 Go 協程才會被認為是可被排程的,所以主協程在這休眠的 10ms 內是不會被再次排程的。因此 主協程
先列印出「 main execution started 」 接著就建立了一個 printHello 協程,需要注意此時的 主協程
還是非休眠狀態的,在這之後主協程就要呼叫休眠函數去睡 10ms,並且把這個控制權讓出來給printHello 協程。printHello 協程會先休眠 1ms 告訴排程器看看有沒有其他可排程的 Go 協程,在這個例子裡顯然沒有其他可排程的 Go 協程了,所以在printHello協程結束了這 1ms 的休眠戶就會被排程器排程,接著就輸出了「 Hello World 」字串,之後這個 Go 協程執行結束。之後,主協程會在之後的幾毫秒被喚醒,緊接著就會輸出「 main execution stopped 」並且結束整個程式。
上面的程式依舊和之前的例子一樣,輸出以下相同的結果:
main execution started
Hello World!
main execution stopped
登入後複製
登入後複製
要是,我把這個printHello 協程中的休眠 1 毫秒改成休眠 15 毫秒,這個結果又是如何呢?
在這個例子中,與其他的例子最大的區別就是printHello 協程比主協程的休眠時間還要長,很明顯,主協程要比 printHello 協程喚醒要早,這樣的結果就是主協程即使喚醒後執行完所有的語句,printHello 協程還是在休眠狀態。之前提到過,主協程比較特殊,如果主協程執行結束後整個程式就要退出,所以 printHello 協程得不到機會去執行下面的輸出的語句了,所以以上的程式的資料結果如下:
main execution started
main execution stopped
登入後複製
登入後複製
就像之前我所提到過的,你可以隨心所欲地建立多個 Go 協程。下面讓我們定義兩個簡單的函數,一個是用於順序列印某個字串中的每個字元,另一個是順序列印出某個整數切片中的每個數位。
在上圖中的程式中,我們連續地建立了兩個 Go 協程,程式輸出的結果如下:
main execution started
H e l l o 1 2 3 4 5
main execution stopped
登入後複製
上面的結果證實了 Go 協程是以合作式排程來運作的。下面我們在每個函數中的輸出語句的下面新增一行 time.Sleep
,讓函數在輸出每個字元或數位後休息一段時間,好讓排程器排程其他可用的 Go 協程。
在上面的程式中,我又修改了一下輸出語句使得我們可以看到每個字元或數位的輸出時刻。理論上主協程會休眠 200ms,因此其他 Go 協程要趕在主協程喚醒之前做完自己的工作,因為主協程喚醒之後就會導致程式退出。getChars
協程每列印一個字元就會休眠 10ms,之後控制權就會傳給 getDigits
協程,getDigits
協程每列印一個數位後就休眠 30ms,若 getChars
協程喚醒,則會把控制權傳回 getChars
協程,如此往復。在程式碼中可以看到,getChars
協程會在其他協程休眠的時候多次進行列印字元以及休眠操作,所以我們預計可以看到輸出的字元比數位更具有連續性。
我們在 Windows 上執行上面的程式,得到了以下的結果:
main execution started at time 0s
H at time 1.0012ms <-|
1 at time 1.0012ms | almost at the same time
e at time 11.0283ms <-|
l at time 21.0289ms | ~10ms apart
l at time 31.0416ms
2 at time 31.0416ms
o at time 42.0336ms
3 at time 61.0461ms <-|
4 at time 91.0647ms |
5 at time 121.0888ms | ~30ms apart
main execution stopped at time 200.3137ms | exiting after 200ms
登入後複製
通過以上輸出結果可以證明我們之前對輸出的討論。對於這個結果,我們可以通過下面的的程式執行圖來解釋。需要注意的是,我們在圖中定義一個輸出語句大約會花費 1ms 的 CPU 時間,而這個時間相對於 200ms 來說是可以忽略不計的。
現在我們已經知道了如何去建立 Go 協程以及去如何去使用它。但是使用 time.Sleep
只是一個讓我們獲取理想結果的一個小技巧。在實際生產環境中,我們無法知曉一個 Go 協程到底需要執行多長的時間,因而在 main 函數裡面新增一個 time.Sleep
並不是一個解決問題的方法。我們希望 Go 協程在執行完畢後告知主協程執行的結果。在目前階段,我們還不知道如何向其他 Go 協程傳遞以及獲取資料,簡而言之,就是與其他 Go 協程進行通訊。這就是 channels 引入的原因。我們會在下一次課中討論這個東西。
如果一個匿名函數可以退出,那麼匿名 Go 協程也同樣可以退出。請參照 課程中的
即時呼叫函數(Immedietly invoked function)
來理解本節。讓我們修改一下之前 printHello
協程的例子:
結果非常明顯,因為我們定義了匿名函數,並在同一語句中作為 Go 協程執行。
需要注意的是,所有的 Go 協程都是匿名的,因為我們從 一課中學到,go 協程是不存在識別符號的,在這裡所謂的匿名 Go 協程只是通過匿名函數來建立的 Go 協程罷了
更多程式設計相關知識,請存取:!!
以上就是go語言協程是什麼的詳細內容,更多請關注TW511.COM其它相關文章!