iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
Software Development

你知道Go是什麼嗎?系列 第 12

Day12 - Goroutine - Golang

  • 分享至 

  • xImage
  •  

今天來提一個大家都說學Go一定要學的東西,goroutine,雖然菜雞如我還沒理解到用途,不過學起來一定不是一件壞事的吧。

併發?並行?

白話一點可以都叫做同時進行,但同時又可以細分為「時間內完成多項任務」與「同一時間做多項任務」

  • 併發Concurrency:共享時間運算,在一段時間內輪流享有時間資源
  • 並行Parallelism:平行運算,一直都能享有時間資源

聽不懂對吧?那再舉個例子

  • 併發:跑步時發現鞋帶掉了,蹲下綁個鞋帶,繼續跑。兩項任務交互切換並完成。
  • 並行:跑步時用手機聽音樂,跑步與聽音樂同時進行著,不會互相干擾。兩項任務同時進行。

這篇文章講的滿清楚的,有興趣可以看一下ˊˇˋ

Goroutine

Goroutine是個輕量的執行序。

A goroutine is a lightweight thread managed by the Go runtime.

直接使用一次就知道了,只要使用go關鍵字去執行func,就能建立一個新的goroutine

package main

import (
    "fmt"
    "time"
)

func Print() {
    for i := 0; ; i++ {
    	fmt.Println("func Print()")
    	time.Sleep(1 * time.Second)
    }
}

func main() {
    go Print()
    fmt.Println("main")
}

output:

main

明明Print()內寫的是無窮迴圈,程式卻printmain就結束了,這是因為main()主程式執行完後會中斷所有goroutine,因此Print()來不及執行就被中斷了。

main()裡面改成下面這樣,幫程式加個暫停器吧

func main() {

    go Print()
    
    for i := 0; i < 2; i++ {
    	fmt.Println("main")
    	time.Sleep(time.Second)
    }
    fmt.Println("main end")
}

output:

main
func Print()
func Print()
main
main end

可以看到main()print()是同時並交錯進行的。多執行幾次還會發現每次結果會不同。

Go的並行

Go在正常情況下會將goroutine分配給不同的邏輯處理器執行,實現程式的並行

func main() {
    go Print("X")
    go Print("O")
    time.Sleep(time.Second * 2)
}

func Print(s string) {
    for i := 0; i < 50; i++ {
    	fmt.Print(s)
    }
}

output:

OOOOXXXXXXXXXXOXXXXXXXOOOOOOOOOOOOOOOOOOXXXXXXXOXXXXXXXXXXXXXXXXOOOOOOOOOOXXXXXXXOOOOOOOOOOOOOOOOXXX

會看到兩個Print()同時都在print東西,是因為goroutine被分配到兩個處理器,同時在執行的關係。

單處理器併發

可以使用runtime.GOMAXPROCS(1)來限制Go能使用的處理器數量,才能看到併發的案例,試試看吧

func main() {
	runtime.GOMAXPROCS(1) // 限制能使用處理器=1

	go Print("X")
	go Print("O")
    
	time.Sleep(time.Second * 2)
}

func Print(s string) {
	for i := 0; i < 50; i++ {
		fmt.Print(s)
	}
}

output:

OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

從這個例子就看得出單處理器狀況下,會先將手上的goroutine處理完,才處理下一個goroutine

搶奪排程

在執行序進入等待狀態時,會先切換到另一個執行序讓他執行,以免浪費時間。我在print內設個time.Sleep(),來看兩個goroutine互相切換執行的樣子。

func main() {
	runtime.GOMAXPROCS(1)

	go Print("X")
	go Print("O")
    
	time.Sleep(time.Second * 2)
}

func Print(s string) {
	for i := 0; i < 50; i++ {
		time.Sleep(time.Microsecond * 1) 
		fmt.Print(s)
	}
}

output:

XOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOX

其中一個goroutine在等待期間時,會交換到另一個goroutine執行,可以看到這邊雖然限制了CPU,卻還是交錯著執行,這邊就是併發的案例。

sync.WaitGroup

上面的例子都是自己設time.Sleep(),實際寫程式時不可能這樣預估時間,因此就要讓程式自己等待排程結束才關閉,就會用到其他套件裡的東西。

WaitGroup的三個方法:

  • Add(i int) : 計數器增加i
  • Done() : 計數器-1,相當於Add(-1)
  • Wait() : 阻塞直到所有的WaitGroup數量變為零,即計數器變為0
var wg sync.WaitGroup // 建立全域變數wg

func main() {
	runtime.GOMAXPROCS(1) // 設置CPU使用
	wg.Add(2) // 添加2筆goroutine訂單
    
	go Print("X")
	go Print("O")
    
	fmt.Println("waiting to finish")
    wg.Wait() // 等待WaitGroup執行完成
	fmt.Println("\nfinish Program")
}

func Print(s string) {
	defer wg.Done() // 操作了一次WaitGroup,要讓wg-1
	for i := 0; i < 50; i++ {
		time.Sleep(time.Microsecond * 1)
		fmt.Print(s)
	}
}

output:

waiting to finish
XOOXXOOXXOOXXOOXXOOXXOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXXOOXO
finish Program

最開始先宣告WaitGroup變數,將要等待的goroutine數量加入,最後等待goroutine都執行完。


參賽前以為每天花個一小時整理文章就好,現在讀懂+整理成文章的時間越來越多,好可怕。今天一直卡在goroutine明明說是併發,執行起來卻像並行,看了好多的案例才看懂,希望我有將我理解到的寫在這篇文章裡ˊˇˋ

參考資料

Golang goroutine
https://ithelp.ithome.com.tw/articles/10204971

Goroutine 讓你用少少的線程, 能接受更多的工作, 但沒說會作比較快
https://ithelp.ithome.com.tw/articles/10218483

Day14 Go併發症狀- Goroutines (go)
https://ithelp.ithome.com.tw/articles/10240892

Day 23 協程同步的三個方法 - WaitGroup
https://ithelp.ithome.com.tw/articles/10226126

使用Golang打造Web應用程式
https://willh.gitbook.io/build-web-application-with-golang-zhtw/02.0/02.7

Golang 入門系列(十五)如何理解go的併發?
https://www.796t.com/content/1568963107.html


上一篇
Day11 - 套件(Package)- Golang
下一篇
Day13 - Channel - Golang
系列文
你知道Go是什麼嗎?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言