goroutine是Go語言中的輕量級線程實現,是建立在線程之上的輕量級的抽象,由Go運行時(runtime)管理。 goroutine允許我們以非常低的代價在同一個位址空間中並行地執行多個函數或方法;相比於線程,它的創建和銷毀的代價要小得多,並且它的調度是獨立於線程的。
本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。
在編寫 Socket 網路程式時,需要事先準備一個執行緒池為每一個 Socket 的收發包分配一個執行緒。開發人員需要在執行緒數量和 CPU 數量間建立一個對應關係,以確保每個任務能及時地被分配到 CPU 上進行處理,同時避免多個任務頻繁地在執行緒間切換執行而損失效率。
雖然,執行緒池為邏輯編寫者提供了執行緒指派的抽象機制。但是,如果面對隨時隨地可能發生的並發和線程處理需求,則線程池就不是非常直觀和方便了。能否有機制:使用者分配足夠的任務,系統能自動幫助使用者把任務分配到 CPU 上,讓這些任務盡量並發運作。這種機制在 Go語言中被稱為 goroutine。
goroutine 是 Go語言中的輕量級線程實現,由 Go 運行時(runtime)管理。 Go 程式會智慧地將 goroutine 中的任務合理地分配給每個 CPU。
Goroutine是建立在執行緒之上的輕量級的抽象。它允許我們以非常低的代價在同一個位址空間中並行地執行多個函數或方法。相比於線程,它的創建和銷毀的代價要小得多,並且它的調度是獨立於線程的。
Go 程式從 main 套件的 main() 函數開始,在程式啟動時,Go 程式就會為 main() 函數建立一個預設的 goroutine。
使用普通函數建立 goroutine
#Go 程式中使用 go 關鍵字為一個函數建立一個 goroutine。一個函數可以被建立多個 goroutine,一個 goroutine 必定對應一個函數。
1) 格式
為一個普通函數建立goroutine 的寫法如下:
go 函数名( 参数列表 )
函數名稱:要呼叫的函數名。
參數清單:呼叫函數需要傳入的參數。
使用 go 關鍵字建立 goroutine 時,被呼叫函數的回傳值會被忽略。
如果需要在 goroutine 中回傳數據,請使用後面介紹的通道(channel)特性,透過通道把資料從 goroutine 中作為傳回值傳出。
2) 範例
使用go 關鍵字,將running() 函數並發執行,每隔一秒鐘列印一次計數器,而main 的goroutine 則等待使用者輸入,兩個行為可以同時進行。請參考下面程式碼:
package main import ( "fmt" "time" ) func running() { var times int // 构建一个无限循环 for { times++ fmt.Println("tick", times) // 延时1秒 time.Sleep(time.Second) } } func main() { // 并发执行程序 go running() // 接受命令行输入, 不做任何事情 var input string fmt.Scanln(&input) }
命令列輸出如下:
#程式碼執行後,命令列會不斷輸出tick,同時可以使用fmt.Scanln () 接受使用者輸入。兩個環節可以同時進行。
程式碼說明如下:
第 12 行,使用 for 形成一個無限迴圈。
第 13 行,times 變數在迴圈中不斷自增。
第 14 行,輸出 times 變數的值。
第 17 行,使用 time.Sleep 暫停 1 秒後繼續循環。
第 25 行,使用 go 關鍵字讓 running() 函數並發運作。
第 29 行,接受使用者輸入,直到按 Enter 鍵時將輸入的內容寫入 input 變數中並返回,整個程式終止。
這段程式碼的執行順序如下圖所示。
圖:並發運行圖
這個範例中,Go 程式在啟動時,執行時(runtime)會預設為 main() 函數建立一個 goroutine。在 main() 函數的 goroutine 中執行到 go running 語句時,歸屬於 running() 函數的 goroutine 被創建,running() 函數開始在自己的 goroutine 中執行。此時,main() 繼續執行,兩個 goroutine 透過 Go 程式的調度機制同時運作。
使用匿名函數建立goroutine
#go 關鍵字後也可以為匿名函數或閉包啟動 goroutine。
1) 使用匿名函數建立goroutine的格式
使用匿名函數或閉包建立goroutine 時,除了將函數定義部分寫在go 的後面之外,還需要加上匿名函數的呼叫參數,格式如下:
go func( 参数列表 ){ 函数体 }( 调用参数列表 )
其中:
參數清單:函數體內的參數變數清單。
函數體:匿名函數的程式碼。
调用参数列表:启动 goroutine 时,需要向匿名函数传递的调用参数。
2) 使用匿名函数创建goroutine的例子
在 main() 函数中创建一个匿名函数并为匿名函数启动 goroutine。匿名函数没有参数。代码将并行执行定时打印计数的效果。参见下面的代码:
package main import ( "fmt" "time" ) func main() { go func() { var times int for { times++ fmt.Println("tick", times) time.Sleep(time.Second) } }() var input string fmt.Scanln(&input) }
代码说明如下:
第 10 行,go 后面接匿名函数启动 goroutine。
第 12~19 行的逻辑与前面程序的 running() 函数一致。
第 21 行的括号的功能是调用匿名函数的参数列表。由于第 10 行的匿名函数没有参数,因此第 21 行的参数列表也是空的。
扩展知识:Goroutine与线程的区别
许多人认为goroutine比线程运行得更快,这是一个误解。Goroutine并不会更快,它只是增加了更多的并发性。当一个goroutine被阻塞(比如等待IO),golang的scheduler会调度其他可以执行的goroutine运行。与线程相比,它有以下的几个优点:
内存消耗更少:
Goroutine所需要的内存通常只有2kb,而线程则需要1Mb(500倍)
创建与销毁的开销更小:
由于线程创建时需要向操作系统申请资源,并且在销毁时将资源归还,因此它的创建和销毁的开销比较大。相比之下,goroutine的创建和销毁是由go语言在运行时自己管理的,因此开销更低。
切换开销更小:
只是goroutine之于线程的主要区别,也是golang能够实现高并发的主要原因。线程的调度方式是抢占式的,如果一个线程的执行时间超过了分配给它的时间片,就会被其他可执行的线程抢占。在线程切换的过程中需要保存/恢复所有的寄存器信息,比如16个通用寄存器,PC(Program Counter)、SP(Stack Pointer)段寄存器等等。而goroutine的调度是协同式的,它不会直接地与操作系统内核打交道。当goroutine进行切换的时候,之后很少量的寄存器需要保存和恢复(PC和SP)。因此goroutine的切换效率更高。
以上是Go語言中goroutine是啥的詳細內容。更多資訊請關注PHP中文網其他相關文章!