目錄

Go routines: Concurrency vs. Parallelism

Go routines 語法

有在寫 Go 程式語言 的人應該知道,它是以「易於併發」著稱。

有多「易」呢?只要在想併發的函數前面加上 go 的字樣就行了。看範例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import "fmt"

func sayHello() {
    for {
        fmt.Println("Hello")
    }
}

func main() {
    go sayHello() // 加上 go 保留字,使用 go routines
    sayHello() // 沒有 go 的字樣,一般的順序執行
}

Concurrency vs. Parallelism

你若是上網搜尋 Go routines 平行運算,應該會很常看到大家在強調 Concurrency 跟 Parallelism 不一樣!

  • Concurrency: 併發
  • Parallelism: 平行

以做早餐舉例

  • 傳統的順序執行:「你先把蛋打到鍋子上煎,等到蛋煎完拿起來後開始煎肉餅,肉餅也煎完後再開始倒牛奶…」聽起來就很慢對吧?
  • Concurrency 併發:「先把蛋打到鍋子上煎,在煎蛋的同時抽空把肉餅也放到鍋子裡,都放完後開始倒牛奶,倒一倒發現蛋有一面熟了跑回來把蛋翻面,接著再回報倒牛奶…」你同一時間還是只有在做一件事,只是透過來回切換可以利用中間等待的時間
  • Parallelism 平行:「有三個廚師:一個負責煎蛋、一個負責煎肉、一個負責倒牛奶。」可想而知這個是最快的。

其實 Parallelism 跟 Concurrency 並不是衝突的兩件事情,一個程式可以既 Parallelism 又 Concurrency,同樣以做早餐舉例就是:「三個廚師各自負責煎蛋、煎肉餅、倒牛奶,煎肉餅的那個廚師自己肉餅放下鍋子在等的時候也跑去幫忙倒牛奶的廚師工作」,在這個例子中,很明顯這樣的效率是最高的。

Go 語言中的 Go routines 其實不只能做 Concurrency,也能 Parallelism,能調用多少個核心是視 GOMAXPROCS 環境變數而定:

  • Go 1.5 版之前:GOMAXPROCS 預設值為 1
  • Go 1.5 版之後:GOMAXPROCS 預設值為 CPU 核心數

GOMAXPROCS 超過 1 的情況下,其實 go routines 的函數值行既是 Concurrency 也是 Parallelism 的。

程式碼實測

來寫一段程式碼測試 Goroutines 在 GOMAXPROCS 大於 1 的情況下是否真的有 Parallelism 平行運算:

 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package main

import (
	"fmt"
	"math"
	"sync"
	"time"
)

const (
	numberRange = 1000000 // Range for finding prime numbers.
)

func main() {
	var wg sync.WaitGroup

	startTime := time.Now()
	// Set the number of processors to be used.
	// runtime.GOMAXPROCS(4)
	findInRange(&wg) // Launch a CPU-bound task
	wg.Wait()
	fmt.Println("Time duration:", time.Now().Sub(startTime))
}

// Search for prime numbers in 4 ranges.
func findInRange(wg *sync.WaitGroup) {
	start := 0
	end := numberRange
	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			findPrimeNumbers(start, end)
			// fmt.Println(findPrimeNumbers(start, end))
		}(i)
		start += numberRange
		end += numberRange
	}
}

// Function that finds prime numbers in a specified range.
func findPrimeNumbers(start, end int) []int {
	var primes []int

	for num := start; num <= end; num++ {
		if isPrime(num) {
			primes = append(primes, num)
		}
	}
	return primes
}

// Check if a number is prime.
func isPrime(num int) bool {
	if num < 2 {
		return false
	}

	limit := int(math.Sqrt(float64(num)))
	for i := 2; i <= limit; i++ {
		if num%i == 0 {
			return false
		}
	}
	return true
}

main 中的 runtime.GOMAXPROCS(1) 會把 GOMAXPROCS 設成 1, 2, 3, 4, 維持預設 來進行測試。

/go-routines-concurrency-parallelism/gomaxprocs-test.webp

可以看到執行的時間很明顯的有差異,代表當 GOMAXPROCS 大於 1 時確實有進行平行運算。

也可以從運作時間觀察到 GOMAXPROCS 預設值確實等於 CPU 核心數。