引言
現在還不能掌握併發編程的程序員,面臨被計算機技術淘汰的窘境。本文注重介紹go語言的goroutine實現併發的編程。
什麼是GoRoutine
Goroutine是與其他函數或方法併發運行的函數或方法,可以將Goroutine視為輕量級線程。與線程相比,創建Goroutine的成本開銷微乎其微。因此,GO應用程序通常同時運行數千個Goroutine。
與線程相比,Goroutine的優勢
- 與線程相比,Goroutine非常節約CPU和內存。它們的堆棧大小隻有幾KB,堆棧可以根據應用程序的需要進行擴展和縮小,而對於線程,必須指定並固定堆棧大小。
- Goroutine被多路複用到較少數量的OS線程。具有數千個Goroutine的程序中可能只有一個線程。如果線程中任何Goroutine阻塞並等待用戶輸入,則創建另一個OS線程,並將剩餘的Goroutine移到新的OS線程。所有這些都由運行時(runtime)負責,作為程序員從這些錯綜複雜的細節中抽象出來,並被賦予一個乾淨的API來處理併發性。
- Goroutine使用通道(channel)進行通信。通道的設計可以防止在使用Goroutine訪問共享內存時出現爭用情況。可以將通道視為Goroutines通信所使用的管道。我們將在下一教程中詳細討論頻道。
開始使用GoRoutine
在函數或方法調用前面加上關鍵字go,您將同時運行一個新的Goroutine。
讓我們創建一個Goroutine:)
<code>package main
import (
"fmt"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
fmt.Println("main function")
}/<code>
在第11行,go hello()開始一個新的Goroutine,hello()函數將與main()函數併發運行。main函數在它自己的Goroutine中運行,它被稱為main Goroutine。
運行這個程序,你會有驚喜哦!
本程序僅輸出了文本函數。那我們開始的Goroutine怎麼樣了?
我們需要了解goroutine的兩個重要特性,才能理解為什麼會發生這種情況。- 當啟動新的Goroutine時,goroutine調用立即返回。與函數不同,該控件不等待Goroutine完成執行。在Goroutine調用之後,程序立即返回到下一行代碼,並忽略來自Goroutine的任何返回值。
- 主Goroutine應該為任何其他Goroutine運行。如果主Goroutine終止,則該程序將終止,並且不會運行其他Goroutine。
我想現在你可以理解為什麼我們的Goroutine沒有跑了。
在第11行調用go hello()之後,程序立即返回到下一行代碼,而無需等待hello goroutine完成。
然後,主Goroutine終止,因為沒有其他代碼要執行,因此hello Goroutine沒有機會運行。
我們對代碼稍作修改。
<code>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")
}/<code>
在上述程序的第13行中,我們調用了time包的Sleep方法,該方法使執行該程序的go例程延時。在這種情況下,主Goroutine會休眠1秒。
現在,go hello()調用在主Goroutine終止之前有足夠的時間執行。此程序首先打印Hello world goroutine,等待1秒,然後打印main函數。
這種在主Goroutine中使用睡眠來等待其他Goroutine完成執行的方式,是我們用來理解Goroutine如何工作的一種技巧。通道(channel)可以用來阻塞主Goroutine,直到所有其他Goroutine完成執行。
啟動多個Goroutine
讓我們再編寫一個啟動多個Goroutine的程序,以便更好地理解Goroutine。
<code>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")
}/<code>
上面的程序在行號中啟動兩個Goroutine,代碼21和22行。這兩個Goroutine現在同時運行。
Goroutine numbers 最初休眠250毫秒,然後打印1,然後再次休眠並打印2,相同的週期發生,直到打印5。類似地,Goroutine alphabets 打印從a到e的字母,並且有400毫秒間隔的休眠時間。
主Goroutine啟動numbers() ,alphabets(),然後休眠3000毫秒,然後終止。
上述代碼運行後輸出內容如下:
<code>1 a 2 3 b 4 c 5 d e main terminated /<code>
代碼不如圖片來的直觀,我們使用下圖描述該程序的工作方式。
藍色的圖像的第一部分代表 numbers Goroutine,栗色的第二部分代表 alphabets Goroutine,綠色的第三部分代表 main Goroutine,最後的部分將上述三部分合並在一起,並向我們展示了程序是如何工作的。
每個框頂部的0毫秒、250毫秒等表示時間(以毫秒為單位),輸出在每個框的底部表示為1、2、3,依此類推。
藍色框告訴我們,1在250ms之後打印,2在500ms之後打印,依此類推。最後一個綜合框的底部有值1a23b4c5de,它也是程序的輸出。
直觀的時序圖將幫助你更好地理解GoRoutine運行的機制。
Happy Coding :-)
我是 ,持續分享編程故事,歡迎關注。
閱讀更多 程序員小助手 的文章