目錄
Runtime 包概述
Runtime 包常用函數
1. GOMAXPROCS
2. Caller 和 Callers
3. BlockProfile 和 Stack
理解Golang的Goroutine
Goroutine的基本概念
特點:
Goroutine的創建與啟動
示例代碼
解釋
Goroutine的調度
Gosched的作用
示例代碼
輸出
解釋
Goroutine的性能優化
示例代碼
解釋
實際應用中的使用場景
總結
Runtime與Routine的關系
補充(代碼)
Runtime 包概述
runtime 包提供了與 Go 運行時環境交互的功能,包括 goroutine 調度、內存管理、堆棧操作等。通過 runtime 包,我們可以更好地控制程序的運行行為。
Runtime 包常用函數
1. GOMAXPROCS
設置可以并行執行的最大 CPU 數,并返回之前的設置值。
fmt.Println(runtime.GOMAXPROCS(2))
2. Caller 和 Callers
runtime.Caller
?和?runtime.Callers
?用于獲取調用棧信息。
pc, file, line, ok := runtime.Caller(1)
if !ok { fmt.Println("runtime.Caller() failed") return
}
fmt.Printf("PC: %v, File: %s, Line: %d\n", pc, file, line)
3. BlockProfile 和 Stack
runtime.BlockProfile()
?用于獲取阻塞概述,而?runtime.Stack()
?則可以打印當前 goroutine 或所有 goroutine 的堆棧跟蹤信息。
理解Golang的Goroutine
Goroutine是Golang語言中的輕量級線程,能夠在單一操作系統線程上運行多個Goroutine,從而提高并發編程的效率。本文將通過實際的代碼示例,詳細講解Goroutine的創建、調度、性能優化以及在實際應用中的使用場景。
Goroutine的基本概念
Goroutine 是Golang語言中用于并發編程的基本單位。與傳統的線程(Thread)相比,Goroutine的調度和切換成本更低,因為它們是基于Golang的協作式調度模型設計的。每個Goroutine的棧大小默認為2KB,但隨著其生命周期的變化,棧大小會自動擴大以適應需求。
特點:
- 輕量級:創建和銷毀Goroutine的開銷非常低。
- 高并發:Golang可以輕松支持數萬甚至更多的Goroutine同時運行。
- ** Channels**:Goroutine之間通過Channel進行通信,避免了共享內存帶來的競態條件問題。
Goroutine的創建與啟動
Goroutine的創建非常簡單,只需要在函數調用前加上go
關鍵字即可啟動一個新的Goroutine。
示例代碼
package mainimport ("fmt""runtime"
)func print() {fmt.Println("這是一個新的Goroutine。")
}func main() {go print() // 啟動一個新的Goroutinefmt.Println("主Goroutine結束。") // 主Goroutine繼續執行
}
解釋
go print()
:啟動一個新的Goroutine執行print
函數。- 主Goroutine繼續執行
fmt.Println("主Goroutine結束。")
。 - 需要注意的是,如果主Goroutine執行完畢,程序可能會直接退出,可能無法看到子Goroutine的輸出。為了確保子Goroutine有足夠的時間完成,可以通過
time.Sleep()
或者其他同步機制來控制。
Goroutine的調度
Goroutine的調度是由Golang的運行時環境負責的。與線程不同,Goroutine采用的是非搶占式調度,即只有在當前Goroutine主動讓出CPU(例如調用runtime.Gosched()
)時,其他Goroutine才能獲取執行機會。
Gosched的作用
runtime.Gosched()
用于讓出當前Goroutine的執行權限,允許其他Goroutine運行。
示例代碼
package mainimport ("fmt""runtime"
)func main() {go func() {for i := 1; i <= 5; i++ {runtime.Gosched()fmt.Println(i)}}()fmt.Println("主Goroutine結束。")
}
輸出
主Goroutine結束。
1
2
3
4
5
解釋
- 子Goroutine在循環中調用
runtime.Gosched()
,主動讓出CPU時間片。 - 主Goroutine在子Goroutine開始后立即執行并輸出“主Goroutine結束。”。
Goroutine的性能優化
Goroutine的性能優化主要體現在以下幾個方面:
-
設置GOMAXPROCS:通過
runtime.GOMAXPROCS(n)
設置可以并行執行的最大CPU核數。合理設置這個值可以提高程序的并行度。- 示例:
runtime.GOMAXPROCS(2)
,表示最多同時使用2個CPU核心。
- 示例:
-
避免競態條件:多個Goroutine訪問共享變量時,可能會出現競態條件。可以通過Channel或sync包中的同步原語(如Mutex、RWMutex)來避免。
-
合理分配任務:在高并發場景下,合理分配任務可以提高程序的執行效率。
示例代碼
package mainimport ("fmt""math""runtime""sync""time"
)func findPrime(num int, inChan chan int, primeChan chan int, exitChan chan bool) {var flag boolfor {v, ok := <-inChanif !ok {break}flag = truefor i := 2; i < int(math.Sqrt(float64(v))); i++ {if v % i == 0 {flag = falsebreak}}if flag {primeChan <- v}}exitChan <- true
}func main() {num := 10000000 // 查找小于num的所有質數start := time.Now()inChan := make(chan int, num)primeChan := make(chan int, num)exitChan := make(chan bool, 4) // 假設使用4個Goroutinego func() {for i := 2; i < num; i++ {inChan <- i}close(inChan)}()for i := 0; i < 4; i++ {go findPrime(num, inChan, primeChan, exitChan)}go func() {for i := 0; i < 4; i++ {<-exitChan}close(primeChan)}()count := 0for v := range primeChan {count++}fmt.Printf("找到%d個質數,用時:%v\n", count, time.Since(start))
}
解釋
- Channel通信:通過Channel傳遞數據,避免了共享變量帶來的競態條件。
- 并行計算:通過啟動多個Goroutine并行計算質數,提高了程序的執行效率。
- 同步機制:通過
exitChan
等待所有Goroutine完成任務。
實際應用中的使用場景
- 并發請求處理:在Web服務器中,通過Goroutine并行處理多個HTTP請求,提高吞吐量。
- 數據處理:在數據處理任務中,通過Goroutine并行處理不同的數據片段,提高處理速度。
- I/O密集型任務:在I/O密集型任務(如文件讀寫、網絡通信)中,Goroutine可以通過非阻塞的方式提高資源利用率。
總結
Goroutine是Golang語言中的并行編程核心,具有輕量級、高效和靈活的特點。通過合理利用Goroutine,可以顯著提高程序的性能和響應速度。在實際應用中,需要注意避免競態條件,合理分配任務,并通過Channel等方式實現Goroutine之間的安全通信。
Runtime與Routine的關系
在Go語言中,runtime
和routine
是兩個密切相關但又有明確區別的概念。runtime
主要指的是Go語言的運行時環境,它為Go程序的執行提供了必要的支持,如內存管理、Goroutine調度、錯誤處理等。而routine
在Go語言中特指Goroutine,即輕量級的線程。
具體來說:
-
Runtime
- 定義:
runtime
是一個Go語言標準庫中的包,提供了與程序運行時環境相關的功能。 - 功能:
- Goroutine調度:管理Goroutine的創建、執行和調度。
- 內存管理:負責內存的分配和回收,包括垃圾回收機制。
- 錯誤處理:提供了一些與錯誤處理相關的函數,如
runtime.Error
。 - 與操作系統交互:處理程序運行時的環境,如線程數、CPU核數等。
- 性能監控:提供了一些與性能監控相關的函數,如內存使用統計。
- 常用函數:
Gosched()
:讓出當前Goroutine的執行權限,允許其他Goroutine運行。GOMAXPROCS()
:設置或獲取可以并行執行用戶級處理的最大CPU核數。NumCPU()
:返回當前可用的CPU核數。Goexit()
:使當前Goroutine退出,然后調度器會打印當前的棧跟蹤信息。
- 定義:
-
Routine(Goroutine)
- 定義:Goroutine是Go語言中的輕量級線性,它由Go的運行時環境管理。
- 特點:
- 輕量級:創建和銷毀Goroutine的開銷比傳統線程低。
- 高并發:可以輕松支持數萬甚至更多的Goroutine同時運行。
- 非阻塞式調度:基于協作式調度,主動讓出CPU時間片。
- 創建方式:通過
go
關鍵字啟動一個新的Goroutine。 - 常見用途:
- 并發執行任務:將一個函數或方法轉換為異步執行,不阻塞主Goroutine。
- 處理I/O密集型任務:在I/O操作中,釋放資源讓其他Goroutine使用。
- 并行計算:在多核CPU上并行執行任務以提高計算效率。
-
兩者的關系
- 調度管理:
runtime
包負責管理Goroutine的調度,包括如何分配時間片、如何在多個CPU核間分配Goroutine等。 - 資源管理:通過
runtime
包,可以控制Goroutine的數量和并行度,優化程序性能。 - 協作式調度:Goroutine通過
runtime.Gosched()
主動讓出CPU時間片,協作式調度依賴于Goroutine自身的配合,而不是操作系統強制切換。
- 調度管理:
補充(代碼)
package mainimport ("fmt""math""runtime""time"
)func PrintCallerInfo() {//返回調用棧的信息(文件名、行號等)。//參數:skip表示從調用Caller開始往回數多少層的調用棧信息。// skip = 0: 返回調用runtime.Caller的函數(即直接包含runtime.Caller(0)調用的那行代碼)的文件名、行號等信息。// skip = 1: 返回調用runtime.Caller所在函數的直接調用者的文件名和行號。換句話說,就是調用了包含runtime.Caller(1)的函數的地方的調用棧信息。// skip = 2: 返回上一步調用者的調用者信息,依此類推。每增加1,就向上追溯一個調用棧幀。// 以此類推,隨著skip值的增加,可以逐級向上追溯到更早的調用棧信息。如果指定的層級超出了實際存在的調用棧層數,則ok將為false,其他返回值(pc, file, line)將沒有意義或為零值。pc, file, line, ok := runtime.Caller(1)if !ok {fmt.Println("runtime.Caller() failed")return}fmt.Printf("PC: %v, File: %s, Line: %d\n", pc, file, line)
}func main() {// 設置可以并行執行的最大CPU數,并返回之前的設置值;控制了Go程序可以同時使用的操作系統線程數量。fmt.Println(runtime.GOMAXPROCS(2))// 啟動一個協程go func(x int) {//time.Sleep(1 * time.Second)fmt.Println("Goroutine", x)}(1)// 終止go func(x int) {defer fmt.Println("Goroutine", x)//time.Sleep(1 * time.Second)runtime.Goexit() // 當前goroutine 退出fmt.Println("This won't print")}(2)// 當前程序中活躍的gotoutines數量fmt.Println("NumGoroutine:", runtime.NumGoroutine())// 讓出當前goroutine的處理器,允許其他等待的goroutines運行;// 這是一個提示,而不是強制性的上下文呢切換go func() {for i := 1; i <= 5; i++ {runtime.Gosched()fmt.Println(i)}}()go func(x int) {//time.Sleep(1 * time.Second)fmt.Println("Goroutine", x)}(3)go func(x int) {//time.Sleep(1 * time.Second)fmt.Println("Goroutine", x)}(4)// 其它//runtime.BlockProfile() //獲取當前的阻塞概要信息(block profile),它用于分析goroutine之間的同步問題,如鎖競爭、通道阻塞等。//runtime.Caller() //返回調用棧的信息(文件名、行號等)。//runtime.Callers() //類似Caller,但返回的是多個調用棧幀的信息。//runtime.GC() //:手動觸發垃圾回收。通常不需要手動調用,Go的垃圾收集器會自動管理內存。fmt.Println(runtime.GOROOT()) // 返回Go的安裝目錄//runtime.KeepAlive(x any) //確保對象在其作用域結束前不會被垃圾回收。//runtime.Stack() //將當前goroutine或所有goroutines的堆棧跟蹤寫入提供的緩沖區。// runtime.Caller//PrintCallerInfo()//time.Sleep(3 * time.Second) // 確保協程有足夠的時間完成//高并發例子FindPrime()//單線程noGoroutine()
}func FindPrime() {var num intvar n intn, _ = fmt.Scanf("%d", &num)fmt.Println(n, num)n = numif num > 32 { // 當num很大時,增大n(即goroutine的數量)卻沒有帶來性能提升甚至導致耗時n = 32}// 對于num=1000000// n = 1, 即noGoroutine(),大約200ms~270ms// n = 2,大約150ms~250ms// n = 4,大約170ms~270ms// n = 8,大約>220ms// 對于num=10000000// n = 1, 即noGoroutine(),大約5s~6s// n = 2,大約2.7s~2.9s// n = 4,大約1.6s~2.0s// n = 8,大約1.8s~2s// n = 16(開始耗時增加), 大約2.1s~2.2s// 對于num=100000000// n = 1, 即noGoroutine(),>100s// n = 4,大約40s~41s// n = 8,大約25s~26s// n = 16, 大約22s~24s// n = 32,大約·32s~33svar primeChan = make(chan int, num)var inChan = make(chan int, num)var exitChan = make(chan bool, n)var start = time.Now()go inPrime(num, inChan)for i := 0; i < n; i++ {go primeJudge(inChan, primeChan, exitChan)}go func() {for i := 0; i < n; i++ {<-exitChan}close(primeChan)}()for {_, ok := <-primeChanif !ok {break}//fmt.Println(res)}close(exitChan)fmt.Println(time.Since(start))
}func inPrime(num int, inChan chan int) {for i := 2; i < num; i++ {inChan <- i}close(inChan)
}func primeJudge(inChan chan int, primeChan chan int, exitChan chan bool) {var flag boolfor {v, ok := <-inChan//fmt.Println("v = ", v, "; ok = ", ok)if !ok {break}flag = truefor i := 2; i < int(math.Sqrt(float64(v))); i++ {if v%i == 0 {flag = falsebreak}}if flag {primeChan <- v}}exitChan <- true
}func noGoroutine() {var num intn, _ := fmt.Scanf("%d", &num)fmt.Println(n, num)var count = 0var start = time.Now()for i := 2; i <= num; i++ {var flag = truefor j := 2; j < (int)(math.Sqrt(float64(i))); j++ {if i%j == 0 {flag = falsebreak}if flag {count++}}}fmt.Println(time.Since(start))
}