介紹Golang 協程實現並行應用
Go是一種令人難以置信的高性能語言。它擁有大量的優秀特性,可以讓你構建出速度驚人的應用程序。通過提供這些goroutine(協程)和channel(通道)機制使得構建並行程序相當簡單。
使用協程可以非常快地把順序應用程序轉成並行,無需擔心創建線程或線程池。但與所有併發編程一樣,這也會帶來一些不可控因素,因此在所有函數調用前使用go關鍵字之前,必須考慮到這些不確定性。本文帶你瞭解如何使用Go協程,提升程序性能。
1. 協程
協程是由Go運行時管理的輕量級線程,讓我們能夠輕鬆創建異步並行程序,異步程序執行速度通常比順序程序要快得多。
協程比線程小得多,它們通常需要2kB的堆棧空間來初始化,而線程需要1Mb的堆棧空間。
Goroutines基於非常少的OS線程實現多路複用,這意味着併發go程序較相同的性能水平Java應用需要更少的資源。創建上千個goroutines通常最多需要一到兩個OS線程,而如果在java中做同樣的事情,則需要1000個完整的線程,每個線程佔用至少1Mb的堆空間。
協程是編譯器級的,進程和線程是操作系統級的。協程不被操作系統內核管理,而完全由程序控制,因此沒有線程切換的開銷。通過將大量個goroutines映射到一個線程上,我們不必擔心在應用程序中創建和銷燬線程時的性能影響。和多線程比,線程數量越多,協程的性能優勢就越明顯。協程的最大優勢在於其輕量級,可以輕鬆創建上萬個而不會導致系統資源衰竭。
2. 示例
爲了演示我們首先創建順序應用,後面再修改爲異步程序。
2.1 順序執行
定義函數簡單根據參數輸出多次信息至控制檯。
package main
import (
"fmt"
"time"
)
// 定義功能簡單的函數,後面進行異步執行
func compute(value int) {
for i := 0; i < value; i++ {
time.Sleep(time.Second)
fmt.Println(i)
}
}
func main() {
fmt.Println("Goroutine Tutorial")
// 順序執行
compute(10)
compute(10)
// 等待控制檯輸入阻止程序結束
var input string
fmt.Scanln(&input)
}
如果執行上述代碼,可以看到在控制檯上打印0~9兩次,整個執行時間超過20秒。 fmt.Scanln() 阻止main程序結束,等待兩個函數順序執行完畢。
2.2 異步執行
如果我們不擔心程序輸出值0到n的順序,那麼我們可以通過使用goroutines並使其異步來加快程序的速度。
package main
import (
"fmt"
"time"
)
// 和前面程序相比沒有改變任何內容
func compute(value int) {
for i := 0; i < value; i++ {
time.Sleep(time.Second)
fmt.Println(i)
}
}
func main() {
fmt.Println("Goroutine Tutorial")
// 僅僅再函數簽名增加了 go 關鍵字
go compute(10)
go compute(10)
var input string
fmt.Scanln(&input)
}
和前面程序相比僅僅增加go關鍵字,但go幫我們創建了兩個獨立的協程並行執行程序。嘗試執行程序,會看到 0,0,1,1,2,2…直到…9,9。如果你計算這個程序的執行時間那麼我們就會注意到一下子降到大約10秒。
這裏要說明下,如果註釋main中最後兩行,執行程序不會再控制檯上打印任何內容。這是因爲main函數在異步函數執行之前已經結束,那麼所有的協程也被迫立刻終止,因此我們增加fmt.Scanln()阻止main函數提前結束。
2.3 匿名協程函數
前面示例使用go 調用命名函數,起始也可以並行執行匿名函數:
package main
import "fmt"
func main() {
// 使用 `go` 創建匿名函數
go func() {
fmt.Println("Executing my Concurrent anonymous function")
}()
// 再次組織main提前結束
fmt.Scanln()
}
執行該程序能看到異步匿名函數成功執行並打印結果:
Executing my Concurrent anonymous function
3. 總結
本文學習了Go中開發並行程序,瞭解協程概念並使用協程加速程序執行、創建高性能應用。