Goroutines 是 Go 語言內建的一種併發(Concurrency)機制,它是一種輕量級的執行單元。Goroutines 與傳統的線程不同,主要是因為它們是由 Go 的運行時(runtime)來管理的,而不是依賴於作業系統的線程。因此,Go 可以在單一程序中高效地管理成千上萬個 Goroutines,而不會產生大量的系統資源開銷。
go func() {
fmt.Println("這是一個 Goroutine!")
}()
ch := make(chan int)
go func() {
ch <- 1 // 傳遞數據
}()
val := <-ch // 接收數據
fmt.Println(val)
Channels 是用來在 Goroutines 之間傳遞資料的管道,它們能保證 Goroutines 之間的通訊是安全且同步的。
ch := make(chan int, 2) // 創建一個緩衝區大小為 2 的 Channel
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
有緩衝的
Channel
允許在不阻塞接收方的情況下,臨時存儲一定數量的數據。
done := make(chan struct{})
go func() {
// 執行某些操作
done <- struct{}{} // 傳送完成信號
}()
<-done // 等待完成信號
fmt.Println("操作已完成")
在某些情況下,可能不需要傳遞數據,而只是發送和接收一個 "完成" 信號。在這種情況下,使用空的 struct{} 可以減少記憶體開銷,因為它不佔任何空間。
defer
確保資源能夠被正確釋放func main() {
done := make(chan struct{})
go func() {
defer close(done) // 確保完成後關閉通道
// 執行一些操作
fmt.Println("正在執行 Goroutine 操作...")
}()
<-done // 等待 Goroutine 完成
fmt.Println("操作已完成")
}
在這個範例中,
defer
保證了無論Goroutine
中的業務邏輯如何,done Channel
都會被關閉。這樣main
函數就能安全地等待通道被關閉,然後繼續執行後續操作。defer
是在函數執行結束前自動執行被延遲的操作,即使函數中間有 return
或發生了錯誤
,defer
的動作依然會被執行。可以在Goroutine
的結束或同步信號傳遞後執行清理工作,這樣可以確保資源不會被洩漏。
context
包進行取消和超時控制import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go func(ctx context.Context) {
select {
case <-time.After(3 * time.Second):
fmt.Println("Goroutine 完成工作")
case <-ctx.Done():
fmt.Println("Goroutine 被取消:", ctx.Err())
}
}(ctx)
// 等待 Goroutine 結束或被取消
time.Sleep(4 * time.Second)
fmt.Println("主函數結束")
}
</* Output: */>
Goroutine 被取消: context deadline exceeded
主函數結束
使用
context
可以方便地控制 Goroutines 的生命週期,避免因為 Goroutines 長時間運行而導致資源泄漏。
select
來處理多個 Channel:ch1 := make(chan int)
ch2 := make(chan string)
go func() {
ch1 <- 1
}()
go func() {
ch2 <- "Hello"
}()
select {
case msg1 := <-ch1:
fmt.Println("接收到數字:", msg1)
case msg2 := <-ch2:
fmt.Println("接收到字串:", msg2)
}
</* Output: */>(非一定,根據執行環境可能會有所不同)
接收到字串: Hello
select
語法的作用是讓程式在多個Channel
中選擇一個可以立即進行操作的通道,並執行相應的邏輯。這裡的邏輯是:
- 如果
ch1
中有數據傳遞,msg1 := <-ch1
會從ch1
讀取數據並賦值給msg1
,然後執行第一個case
,輸出 "接收到數字" 和相應的數據。- 如果
ch2
中有數據傳遞,msg2 := <-ch2
會從ch2
讀取數據並賦值給msg2
,然後執行第二個case
,輸出 "接收到字串" 和相應的數據。
由於兩個 Goroutines 是並行運作的,這裡的程式有一個無法預測的執行順序問題。ch1 和 ch2 中的數據可能會以任何順序到達,因此 select 語句最終執行哪一個 case 取決於哪個 Channel 首先準備好數據。
select
來處理資源管理與清理
import (
"fmt"
"time"
)
func main() {
done := make(chan struct{})
go func() {
defer close(done)
// 執行操作
}()
select {
case <-done:
fmt.Println("Goroutine 完成")
case <-time.After(5 * time.Second):
fmt.Println("操作超時")
}
}
select
可以設置超時機制,避免因為等待過久而導致資源無法釋放。select
語句會等待至少一個 Channel
可用時才繼續執行。這裡的 select
不會在兩個 Channel
都未準備好時立即執行,而是會阻塞直到其中一個 Channel
傳遞數據。Channel
傳遞了數據,對應的 case
就會被執行,程式不會同時執行兩個 case
。調度模型:Go 的運行時使用 M
調度器,將 M 個 Goroutines 映射到 N 個操作系統線程(OS Threads)上。這種模型允許 Go 高效地管理大量 Goroutines,而不會造成過多的系統資源消耗。
GOMAXPROCS 的設置:GOMAXPROCS 控制著 Go 程式同時運行的最大 CPU 核心數量。通過調整 GOMAXPROCS,你可以影響程式的併發性能,特別是在多核處理器上運行時。
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // 查看當前 GOMAXPROCS 設置
runtime.GOMAXPROCS(2) // 設置使用 2 個 OS 線程
fmt.Println("GOMAXPROCS 設置為:", runtime.GOMAXPROCS(0))
}
</* Output: */>
GOMAXPROCS: 8
GOMAXPROCS 設置為: 2
初始的
GOMAXPROCS
通常設置為可用的 CPU 核心數量。
通過runtime.GOMAXPROCS(n)
,可以將GOMAXPROCS
設置為n
,這會影響 Go 調度器同時運行的 OS 線程數量。
type Task struct {
done chan struct{}
}
func (t *Task) DoWork() {
go func() {
defer close(t.done) // 確保任務完成後關閉通道
// 執行業務邏輯
// ...
t.done <- struct{}{} // 同步完成信號
}()
}
func main() {
task := &Task{done: make(chan struct{})}
task.DoWork()
<-task.done // 等待任務完成
fmt.Println("任務已完成")
}
Goroutines
中,通過 Channels
傳遞信號來控制同步。在 Go 語言中,Goroutines 提供了一種輕量且高效的併發處理機制。相較於傳統的線程,Goroutines 具有更好的資源利用率,啟動內存開銷非常小,並且由 Go 的運行時系統自動管理和調度,避免了手動管理線程的複雜性。Channels
是 Goroutines
之間通訊的關鍵,保證了數據傳遞的安全性和同步性。使用 struct{}
型別的 Channel
,可以傳遞信號而不涉及額外的數據開銷,節省資源。通過搭配 defer
,能夠確保資源在 Goroutine
完成後被正確釋放。select
語法允許程式在多個 Channel
中進行選擇,這讓開發者能夠在多個併發操作中動態決定接收數據的順序。Goroutines
的執行是並行的,這使得數據傳遞的順序難以預測,但通過 select
,程式可以高效處理多個 Goroutines
的通訊。