Go語言的協程——Goroutine

程序(Process),執行緒(Thread),協程(Coroutine,也叫輕量級執行緒)

程序程序是一個程式在一個數據集中的一次動態執行過程,可以簡單理解為“正在執行的程式”,它是CPU資源分配和排程的獨立單位。 程序一般由程式、資料集、程序控制塊三部分組成。我們編寫的程式用來描述程序要完成哪些功能以及如何完成;資料集則是程式在執行過程中所需要使用的資源;程序控制塊用來記錄程序的外部特徵,描述程序的執行變化過程,系統可以利用它來控制和管理程序,它是系統感知程序存在的唯一標誌。

程序的侷限是建立、撤銷和切換的開銷比較大。

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

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

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

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

Goroutine

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

main

import

“fmt”

func

hello

()

{

fmt

Println

“Hello world goroutine”

}

func

main

()

{

go

hello

()

fmt

Println

“main function”

}

執行結果:可能會值輸出“main function”。

GO語言基礎進階教程:Go語言的協程——Goroutine

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

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

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

修改以上程式碼:

package

main

import

“fmt”

“time”

func

hello

()

{

fmt

Println

“Hello world goroutine”

}

func

main

()

{

go

hello

()

time

Sleep

1

*

time

Second

fmt

Println

“main function”

}

執行結果:

GO語言基礎進階教程:Go語言的協程——Goroutine

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

1.4 啟動多個Goroutines

示例程式碼:

package

main

import

“fmt”

“time”

func

numbers

()

{

for

i

:=

1

i

<=

5

i

++

{

time

Sleep

250

*

time

Millisecond

fmt

Printf

“%d ”

i

}

}

func

alphabets

()

{

for

i

:=

‘a’

i

<=

‘e’

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語言基礎進階教程:Go語言的協程——Goroutine