Like Share Discussion Bookmark Smile

J.J. Huang   2025-01-06   Getting Started Golang 00.簡單介紹   瀏覽次數:次   DMCA.com Protection Status

Go | 超級簡單的學會用並行

💬簡介

Golang 的並發簡單使用:GoroutineChannel

隨著多核處理器的普及,程式語言也開始朝向並行化發展。Go 語言(Golang)在這個背景下誕生,並且原生支援並發程式設計。與許多傳統語言不同,Go 提供了一種非常簡單的方式來處理並發,開發者可以輕鬆利用多核 CPU 提升效能。

圖片來源:Gophers (地鼠造型的原創者為 Renee French)


👥並發機制:Goroutine 和 Channel

Go 的並發機制基於兩個重要的概念:goroutine 和 channel。

  • Goroutine:是輕量級的執行單元,類似於「輕量級的線程」。開發者只需使用 go 關鍵字啟動一個 goroutine,程式運行時會自動調度這些 goroutine 到不同的 CPU 核心上執行,開發者無需手動管理。

  • Channel:是用來進行 goroutine 之間通訊的管道。Channel 使得不同 goroutine 之間可以安全地交換資料,並且簡化了同步操作。資料可以透過 Channel 發送和接收,開發者無需擔心複雜的鎖或競爭條件。

📌簡單的 Goroutine 與 Channel 範例

這是最基礎的示範,展示如何使用 goroutine 和 channel 執行並發操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"time"
)

// 並發執行的函式
func greet(name string, channel chan<- string) {
for i := 1; i <= 3; i++ { // 模擬3次工作的過程
time.Sleep(time.Millisecond * 500) // 模擬一些工作
fmt.Printf("Greet %d: Hello, %s!\n", i, name) // 顯示正在執行的過程
}
// 最後發送消息
channel <- fmt.Sprintf("Finished greeting: %s", name)
}

func main() {
// 創建一個通道
messageChannel := make(chan string)

// 啟動兩個 goroutine
go greet("Alice", messageChannel)
go greet("Bob", messageChannel)

// 接收並顯示來自 goroutine 的消息
fmt.Println(<-messageChannel)
fmt.Println(<-messageChannel)
}

🖨輸出:

1
2
3
4
5
6
7
8
Greet 1: Hello, Alice!
Greet 1: Hello, Bob!
Greet 2: Hello, Bob!
Greet 2: Hello, Alice!
Greet 3: Hello, Alice!
Finished greeting: Alice
Greet 3: Hello, Bob!
Finished greeting: Bob

📌解析:

  • greet 函式:在兩個 goroutine 中並行運行,每個函式模擬多次工作並將結果發送到通道。
  • 主程序從通道接收兩個 goroutine 的結果並輸出,顯示它們並行運行的結果。

🚀為什麼 Golang 的並發如此簡單?

  • 輕量的 goroutine:
    • Goroutine 不像傳統線程那樣需要大量記憶體和開銷,每個只需幾 KB 的記憶體,使得同時運行成千上萬的 goroutine 成為可能。
  • 自動調度:
    • 運行時會自動將 goroutine 調度到不同的 CPU 核心,開發者無需關心執行順序或負載均衡問題,專注於業務邏輯即可。
  • 簡單的通訊:
    • Channel 提供直觀的資料傳遞方式,讓不同 goroutine 之間能夠進行同步,而無需擔心鎖操作。通訊模型(CSP)避免資料競爭,簡單易懂。
  • 不需手動管理線程:
    • 傳統多線程需要手動創建線程、管理線程池、設置鎖等,而 goroutine 和 channel 封裝了這些操作,極大簡化了並發設計。

♾️更多範例

【範例】<點擊>

範例 1:計算平方的生產者與消費者模型

這個範例展示了如何實現生產者與消費者模式,並利用 goroutine 和 channel 將數據傳遞給消費者進行處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
"time"
)

// 生產者:產生數字並發送到通道
func producer(channel chan<- int) {
for i := 1; i <= 5; i++ {
channel <- i // 發送數字到通道
time.Sleep(time.Second)
}
close(channel) // 關閉通道,告訴消費者不再有新數據
}

// 消費者:從通道中接收數字並計算平方
func consumer(channel <-chan int) {
for num := range channel {
fmt.Printf("Square of %d is %d\n", num, num*num)
}
}

func main() {
// 創建通道
channel := make(chan int)

// 啟動生產者與消費者 goroutine
go producer(channel)
consumer(channel)
}

解析:

  • 生產者每秒生產一個數字並通過通道發送給消費者。
  • 消費者接收到數字後,計算並顯示其平方值。

範例 2:計算總和的並行處理

這個範例展示如何使用多個 goroutine 並行計算多個數字陣列的總和,然後將結果發送到主函式中合併。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"fmt"
"time"
)

// 計算總和的函式
func sum(start, end int, result chan<- int) {
total := 0
for i := start; i <= end; i++ {
total += i
}
result <- total // 將結果發送到通道
}

func main() {
// 定義數字範圍
ranges := []struct{ start, end int }{
{1, 25},
{26, 50},
{51, 75},
{76, 100},
}

// 創建通道來接收結果
resultChannel := make(chan int, len(ranges))

// 啟動多個 goroutine 來計算範圍的總和
for _, r := range ranges {
go sum(r.start, r.end, resultChannel)
}

// 等待所有的 goroutine 完成計算
total := 0
for i := 0; i < len(ranges); i++ {
total += <-resultChannel
}

// 顯示總和
fmt.Printf("Total sum is: %d\n", total)
}

解析:

  • 程式將數字範圍分成四段,並使用四個 goroutine 並行計算每段的總和。
  • 最後,主函式從通道中接收結果並將其加總。

範例 3:並行網頁請求

這個範例展示了如何並行發送多個 HTTP 請求並處理回應。這在需要並行處理多個網頁的情境下很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"
"net/http"
"time"
)

// 發送 HTTP 請求的函式
func fetchURL(url string, channel chan<- string) {
resp, err := http.Get(url)
if err != nil {
channel <- fmt.Sprintf("Error fetching %s: %v", url, err)
return
}
defer resp.Body.Close()
channel <- fmt.Sprintf("Fetched %s with status: %s", url, resp.Status)
}

func main() {
// 目標網址
urls := []string{
"https://golang.org",
"https://www.example.com",
"https://www.wikipedia.org",
}

// 創建通道來接收結果
channel := make(chan string, len(urls))

// 啟動 goroutine 來發送多個請求
for _, url := range urls {
go fetchURL(url, channel)
}

// 等待並顯示結果
for range urls {
fmt.Println(<-channel)
}
}

解析:

  • 每個 HTTP 請求都是在一個單獨的 goroutine 中並行發送的,然後將結果發送到通道中。
  • 主函式從通道中接收回應並顯示結果。

範例 4:並發處理大量任務

這個範例演示如何利用 goroutine 並行處理大量任務,並發送任務完成的通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"time"
)

// 任務處理函式
func processTask(taskId int, channel chan<- string) {
// 模擬工作
time.Sleep(time.Millisecond * 500)
channel <- fmt.Sprintf("Task %d completed!", taskId)
}

func main() {
// 創建通道
channel := make(chan string)

// 並行啟動大量任務
for i := 1; i <= 10; i++ {
go processTask(i, channel)
}

// 收集所有任務完成的結果
for i := 1; i <= 10; i++ {
fmt.Println(<-channel)
}
}

解析:

  • 我們創建了 10 個 goroutine 來處理 10 個任務,並在每個任務完成時將結果發送到通道。
  • 主函式等待並顯示所有任務完成的結果。

🎯總結

Go 的並發設計簡單而強大。開發者只需使用 go 關鍵字啟動 goroutine,並透過 channel 進行通信,便能輕鬆實現並發程式。運行時自動處理線程調度和同步,讓開發者專注於業務邏輯,而無需關心底層細節。

在本篇範例中,我們展示了如何使用 goroutine 和 channel 在 Go 語言中實現並發的基本概念。雖然這些範例沒有深入到更複雜的同步機制或錯誤處理,但它們已經展示了並發程式設計的基本思路。後續會深入分析 Go 語言中的並發機制,並介紹如何處理更高級的並發場景與挑戰。

最後建議回顧一下Go | 菜鳥教學目錄,了解其章節目錄。


註:以上參考了
Go
維基百科 - Go
ithome - Go 的並發:Goroutine 與 Channel 介紹(Peter Chen)
用 10 分鐘了解 Go 語言如何從 Channel 讀取資料(小惡魔 - AppleBOY)