GO語言:協程——Goroutine

2020-12-13 騰訊網

Go語言的協程——Goroutine

進程(Process),線程(Thread),協程(Coroutine,也叫輕量級線程)

進程進程是一個程序在一個數據集中的一次動態執行過程,可以簡單理解為「正在執行的程序」,它是CPU資源分配和調度的獨立單位。 進程一般由程序、數據集、進程控制塊三部分組成。我們編寫的程序用來描述進程要完成哪些功能以及如何完成;數據集則是程序在執行過程中所需要使用的資源;進程控制塊用來記錄進程的外部特徵,描述進程的執行變化過程,系統可以利用它來控制和管理進程,它是系統感知進程存在的唯一標誌。 進程的局限是創建、撤銷和切換的開銷比較大。

線程線程是在進程之後發展出來的概念。 線程也叫輕量級進程,它是一個基本的CPU執行單元,也是程序執行過程中的最小單元,由線程ID、程序計數器、寄存器集合和堆棧共同組成。一個進程可以包含多個線程。 線程的優點是減小了程序並發執行時的開銷,提高了作業系統的並發性能,缺點是線程沒有自己的系統資源,只擁有在運行時必不可少的資源,但同一進程的各線程可以共享進程所擁有的系統資源,如果把進程比作一個車間,那麼線程就好比是車間裡面的工人。不過對於某些獨佔性資源存在鎖機制,處理不當可能會產生「死鎖」。

協程協程是一種用戶態的輕量級線程,又稱微線程,英文名Coroutine,協程的調度完全由用戶控制。人們通常將協程和子程序(函數)比較著理解。 子程序調用總是一個入口,一次返回,一旦退出即完成了子程序的執行。

與傳統的系統級線程和進程相比,協程的最大優勢在於其"輕量級",可以輕鬆創建上百萬個而不會導致系統資源衰竭,而線程和進程通常最多也不能超過1萬的。這也是協程也叫輕量級線程的原因。

協程的特點在於是一個線程執行,與多線程相比,其優勢體現在:協程的執行效率極高。因為子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數量越多,協程的性能優勢就越明顯。Goroutine1.1 什麼是Goroutine

go中使用Goroutine來實現並發concurrently。

Goroutine是Go語言特有的名詞。區別於進程Process,線程Thread,協程Coroutine,因為Go語言的創造者們覺得和他們是有所區別的,所以專門創造了Goroutine。

Goroutine是與其他函數或方法同時運行的函數或方法。Goroutines可以被認為是輕量級的線程。與線程相比,創建Goroutine的成本很小,它就是一段代碼,一個函數入口。以及在堆上為其分配的一個堆棧(初始大小為4K,會隨著程序的執行自動增長刪除)。因此它非常廉價,Go應用程式可以並發運行數千個Goroutines。

Goroutines在線程上的優勢。

與線程相比,Goroutines非常便宜。它們只是堆棧大小的幾個kb,堆棧可以根據應用程式的需要增長和收縮,而在線程的情況下,堆棧大小必須指定並且是固定的

Goroutines被多路復用到較少的OS線程。在一個程序中可能只有一個線程與數千個Goroutines。如果線程中的任何Goroutine都表示等待用戶輸入,則會創建另一個OS線程,剩下的Goroutines被轉移到新的OS線程。所有這些都由運行時進行處理,我們作為程式設計師從這些複雜的細節中抽象出來,並得到了一個與並發工作相關的乾淨的API。

當使用Goroutines訪問共享內存時,通過設計的通道可以防止競態條件發生。通道可以被認為是Goroutines通信的管道。

1.2 主goroutine

封裝main函數的goroutine稱為主goroutine。

主goroutine所做的事情並不是執行main函數那麼簡單。它首先要做的是:設定每一個goroutine所能申請的棧空間的最大尺寸。在32位的計算機系統中此最大尺寸為250MB,而在64位的計算機系統中此尺寸為1GB。如果有某個goroutine的棧空間尺寸大於這個限制,那麼運行時系統就會引發一個棧溢出(stack overflow)的運行時恐慌。隨後,這個go程序的運行也會終止。

此後,主goroutine會進行一系列的初始化工作,涉及的工作內容大致如下:

創建一個特殊的defer語句,用於在主goroutine退出時做必要的善後處理。因為主goroutine也可能非正常的結束

啟動專用於在後臺清掃內存垃圾的goroutine,並設置GC可用的標識

執行mian包中的init函數

執行main函數

執行完main函數後,它還會檢查主goroutine是否引發了運行時恐慌,並進行必要的處理。最後主goroutine會結束自己以及當前進程的運行。

1.3 如何使用Goroutines

在函數或方法調用前面加上關鍵字go,您將會同時運行一個新的Goroutine。

實例代碼:

package mainimport (

"fmt")func hello() {

fmt.Println("Hello world goroutine")}func main() {

go hello()

fmt.Println("main function")}

運行結果:可能會值輸出「main function」。

我們開始的Goroutine怎麼樣了?我們需要了解Goroutine的規則

當新的Goroutine開始時,Goroutine調用立即返回。與函數不同,go不等待Goroutine執行結束。當Goroutine調用,並且Goroutine的任何返回值被忽略之後,go立即執行到下一行代碼。

main的Goroutine應該為其他的Goroutines執行。如果main的Goroutine終止了,程序將被終止,而其他Goroutine將不會運行。

修改以上代碼:

package mainimport (

"fmt"

"time")func hello() {

fmt.Println("Hello world goroutine")}func main() {

go hello()

time.Sleep(1 * time.Second)

fmt.Println("main function")}

運行結果:

在上面的程序中,我們已經調用了時間包的Sleep方法,它會在執行過程中睡覺。在這種情況下,main的goroutine被用來睡覺1秒。現在調用go hello()有足夠的時間在main Goroutine終止之前執行。這個程序首先列印Hello world goroutine,等待1秒,然後列印main函數。

1.4 啟動多個Goroutines

示例代碼:

package mainimport (

"fmt"

"time")func numbers() {

for i := 1; i

time.Sleep(250 * time.Millisecond)

fmt.Printf("%d ", i)

}}func alphabets() {

for i := 'a'; i

time.Sleep(400 * time.Millisecond)

fmt.Printf("%c ", i)

}}func main() {

go numbers()

go alphabets()

time.Sleep(3000 * time.Millisecond)

fmt.Println("main terminated")}

運行結果:

1 a 2 3 b 4 c 5 d e main terminated

時間軸分析:

相關焦點

  • Go 基礎之 Goroutine
    原文如下:這篇文章將繼續關注 Go 語言基礎部分。我們將討論關於性能方面的一些知識,並通過創建一些簡單的 goroutine 來擴展我們的應用程式。我們還會關注一些 Go 語言的底層執行邏輯以及 Go 語言與其他語言的不同之處。Go 語言的並發繼續討論之前,我們必須理解並發與並行的概念。Golang 可以實現並發和並行。
  • Go語言潛力有目共睹,但它的Goroutine機制底層原理你了解嗎?
    來源 | 後端技術指南針(ID:gh_ed1e2b37dcb6)Go語言的巨大潛力有目共睹,今天我們來學習Go語言的Goroutine機制,這也可能是Go語言最為吸引人的特性了,理解它對於掌握Go語言大有裨益,話不多說開始吧!
  • 更便捷的goroutine控制利器- Context
    Context 多協程安全,可以在多個協程中放心使用。go Context定義Context 是Go 1.7 標準庫引入 的標準庫,中文譯作「上下文」,準確說它是 goroutine 的上下文,包含 goroutine 的運行狀態、環境、現場等信息。
  • Goroutine
    接下來我們在調用hello函數前面加上關鍵字go,也就是啟動一個goroutine去執行hello這個函數。func main() { go hello() // 啟動另外一個goroutine去執行hello函數 fmt.Println("main goroutine done!")
  • Go語言愛好者周刊:第 77 期 — 這道題目測答對的人不多
    題圖:excelize 2.3.2 發布刊首語 以下代碼輸出結果中, goroutine 的數量是幾個?2、你遇到過哪些高質量的 Go 語言面試題?來自知乎的一個問題和一些答案的整理。3、深入理解 Go 語言的類型無論什麼語言,類型都涉及到了編程語法的方方面面。
  • 一篇文章帶你入門Go語言基礎之並發
    引言Go語言,專門為並發而生的語言,每啟動一個微線程創建一個代價大概2KB起步假設一個內存條大小4G,一個微線程2kb,1G=1024M=1048576kb,1048576/2=524288,五十多萬個但是你知道像Java,Python等語言,一個線程代價多大嗎???
  • Go 1.2發布 著重語言特性的改進
    最新發布的版本對語言特性做了些改進、性能優化、添加和修改一些標準庫(向後兼容)。下面一起來看下有哪些新特性和改進的地方:nil指針語法;三索引片段語法:開發人員只能根據自己所傳遞的數值來訪問底層數組,類似:slice = array[2:4:7];增加了計算和顯示測試覆蓋率結果的工具,包括go test和Cover;協程的調度改為搶佔式的
  • 乾貨 | Go/Python/Erlang程式語言對比分析及示例
    Go的很多語言特性借鑑與它的三個祖先:C,Pascal和CSP。Go的語法、數據類型、控制流等繼承於C,Go的包、面對對象等思想來源於Pascal分支, 而Go最大的語言特色,基於管道通信的協程並發模型,則借鑑於CSP分支。
  • Go語言和Java、Python等其他語言的對比分析
    這是因為Go提供了軟體生命周期(開發、測試、部署、維護等等)的各個環節的工具,如go tool、gofmt、go test。三、對比其他語言Go的很多語言特性借鑑與它的三個祖先:C,Pascal和CSP。
  • Go語言愛好者周刊:第 23 期
    )大神是如何學習 Go 語言之淺入淺出接口的原理4)大神是如何學習 Go 語言之寫出優雅的 Go 代碼3、使用多年的 go pprof 檢查內存洩漏的方法居然是錯的?!Go 是谷歌創建的一門程式語言,它通過輕量級線程(協程)提供了容易理解的並發抽象。減輕了並發編程的難度。為了支持這些 Goroutine,Go 使用 runtime 將 Goroutine 多路復用到 OS 線程上,為了簡化磁碟 IO,Go 保留了一組 IO 線程,用於阻塞 IO 以提升 CPU 的使用率。
  • Go 經典入門系列 32:panic 和 recover
    在 Go 語言中,程序中一般是使用錯誤[2]來處理異常情況。對於程序中出現的大部分異常情況,錯誤就已經夠用了。但在有些情況,當程序發生異常時,無法繼續運行。在這種情況下,我們會使用 panic 來終止程序。
  • 一文帶你解密 Go 語言之通道 channel
    Go 語言中的一大利器那就是能夠非常方便的使用 go 關鍵字來進行各種並發,而並發後又必然會涉及通信。可以知道 channel 就是用於 goroutine 的數據通信:演示代碼如下:func main() { ch := make(chan string) go func() {  ch <- "煎魚" }() msg :=
  • go 學習筆記之解讀什麼是defer延遲函數
    當前協程正驚慌失措because the corresponding goroutine is panicking周圍函數萬一發生 panic 時也會先運行前面已經定義好的 defer 語句,而 panic
  • 面試題:Goroutine 初始佔用多大棧空間?是如何演進的
    本文基於 Go 1.12Go 提供了一套簡單且智能的協程管理,簡單是因為協程在最開始的時候只有 2Kb 大小,智能是指協程的大小是能隨著實際情況變大縮小的。在不同的版本,有些設定是不一樣的,比如:Go 1.2[1] :協程的堆棧大小從 4Kb 增加到 8Kb。
  • Go Mutex 之4種易錯場景盤點
    除此以外,還可以用 go vet 工具主動檢查死鎖。run 會報錯,通過 go vet main.go,結果如下:main.go:11:14: copyF passes lock by value: sync.Mutexmain.go:26:8: call of copyF copies lock value: sync.Mutex如果只是單純的複製 Mutex,不使用的話
  • 深度解密Go語言之pprof
    關於 goroutine 的信息有兩個連結, goroutine 和 full goroutine stackdump,前者是一個匯總的消息,可以查看 goroutines 的總體情況,後者則可以看到每一個 goroutine 的狀態。頁面具體內容的解讀可以參考【大彬】的文章。
  • 計算機語言協程的歷史、現在和未來
    協程(Coroutine)的出現也不例外。協程的概念,最早可以追溯到解決COBOL語言編譯器中的技術難題。從磁帶到協程COBOL是最早的高級語言之一,編譯器則是高級語言必不可少的一部分。現如今,我們對編譯器的了解,已經到了可以把核心內容濃縮成一本教科書的程度。然而在二十世紀六十年代,如何寫作高效的語言編譯器還是繞不過的現實問題。
  • Go 經典入門系列 24:Select
    在上述程序裡,server1 協程會在 6 秒之後寫入 output1 信道,而server2 協程在 3 秒之後就寫入了 output2 信道。因此 select 語句會阻塞 3 秒鐘,等著 server2 向 output2 信道寫入數據。