您好,登錄后才能下訂單哦!
這篇文章主要講解了“go語言協程指的是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“go語言協程指的是什么”吧!
在go語言中,協程(goroutine)是指在后臺中運行的輕量級執行線程;go協程是Go中實現并發的關鍵組成部分。Go中提供了一個關鍵字go來創建一個Go協程,當在函數或方法的調用之前添加一個關鍵字go,這樣就開啟了一個Go協程,該函數或者方法就會在這個Go協程中運行。
Go 協程 (goroutine) 是指在后臺中運行的輕量級執行線程,go 協程是 Go 中實現并發的關鍵組成部分。
由于 Go 協程相對于傳統操作系統中的線程 (thread) 是非常輕量級的,因此對于一個典型的 Go 應用來說,有數以千計的 Go 協程并發運行的情形是十分常見的。并發可以顯著地提升應用的運行速度,并且可以幫助我們編寫關注點分離(Separation of concerns,Soc)的代碼。
我們也許在理論上已經知曉 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 協程也同樣可以退出。請參照functions
課程中的 即時調用函數(Immedietly invoked function)
來理解本節。讓我們修改一下之前 printHello
協程的例子:
結果非常明顯,因為我們定義了匿名函數,并在同一語句中作為 Go 協程執行。
需要注意的是,所有的 Go 協程都是匿名的,因為我們從并發(concurrency
一課中學到,go 協程是不存在標識符的,在這里所謂的匿名 Go 協程只是通過匿名函數來創建的 Go 協程罷了。
感謝各位的閱讀,以上就是“go語言協程指的是什么”的內容了,經過本文的學習后,相信大家對go語言協程指的是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。