iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 14
2
Software Development

Go繁不及備載系列 第 14

# Day14 Go併發症狀- Goroutines (go)

  • 分享至 

  • xImage
  •  

Day14 Go併發症狀- Goroutines (go)

併發是什麼症狀

是併發運算,不是某種併發症。
就是多線程運算。

  • 併發(Concurrency) 不是併行(Parallelism)

雖然兩者在口語表達上都會說成『同時進行』,但是中文的『同時』很難區分是併行還是併發
但從英文單字來看的話,會比較容易理解

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

併發是把時間切成很小很小段,在這小段的時間裡先後執行多項任務。
併行是CPU有多個核心,可以同時處理多個任務。

簡單來說,拿人來比喻的話:
併發一個人在一段時間內做兩件事
併行兩個人同時在做事

Go併發(Goroutines)

Goroutine是輕量級的線程。

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

main func則是程式當前的、主要的goroutine

想要讓Go併發並ㄅㄧㄤˋ叫非常的容易,
只要寫好一個func,在呼叫時多一個go關鍵字即可。
多線程讓你的程式嚇嚇叫。

【Go Func】

https://py.golang.org/p/QQWHnuHJdkv

package main

import (
	"fmt"
	"time"
)

func main() {
	go test()
}

func test() {
	time.Sleep(time.Second * 1)
	fmt.Println("嚇嚇叫")
}

/* result:

*/

疑?奇怪,怎麼沒有輸出,是我的Go死掉了嗎?
說好的嚇嚇叫呢?

非也。
主程式func main確實已經執行完畢了,
但是被你go出去的func還在time.Sleep,而且睡的很爽。
這可是一秒鐘耶,對電腦來講天文數字啊!

所以被你go出去的func睡飽回來發現main主線程已經早已結束返回,
自然看不見fmt.Print的輸出了。

稍微改一下,給主線程睡一會,這樣就可以了。

https://play.golang.org/p/Mn9klYHEmU-


func main() {
	go test()
	time.Sleep(time.Second * 1)
}

func test() {
	fmt.Println("嚇嚇叫")
}

/* result:
嚇嚇叫
*/

【Go併發過程】

GO的併發會用到多個核心下去執行,試著執行以下的程式看看,
輸出是一段一段的,一下一下交錯著,代表兩邊的線程都很努力的想噴射把值Print出來。
https://play.golang.org/p/Y29gVHLFTUC

func main() {
	go print1()
	go print2()
	time.Sleep(time.Second)
}

func print1() {
	for i := 0; i < 1000; i++ {
		fmt.Print("O")
	}
}

func print2() {
	for i := 0; i < 1000; i++ {
		fmt.Print("-")
	}
}

/* result:
OOOOOOOOOOOOOOOOOOOOOOOOO--------OO-----------OOOOOOOOOOO...
*/

【runtime.GOMAXPROCS】

runtime.GOMAXPROCS(n) 這一參數限制程式執行時 CPU用到的最大核心數量。
如果設置小於1,等於沒設,預設值是電腦核心數。

GOMAXPROCS sets the maximum number of CPUs that can be executing.
If n < 1, it does not change the current setting.

https://play.golang.org/p/idAyE6AsCJ1

但限制了一核心之後,為什麼還是可以把兩個print func都印到呢,
怎麼不是只印出一個直到時間到?說好的單核心?

原來是Go Routine會去排程,執行A線程一小段時間後會跳到線程B去,
這才公平合理嘛!不然CPU資源都被其中一個線程給佔住,作業系統就卡死啦。
所以這次看到的輸出會是 很多 再來很多,兩者都連續印很多的情況下交錯著。

【匿名函式】

匿名函式(Anonymous Function)是什麼?
第一次看到這麼名字時不明覺厲,可能跟匿名者聯想到了一起吧,
但事實上用匿名函式並不是多厲害的事情,而是因為簡便
因為我懶得再幫你這個func提出去 取一個好名字 再呼叫名字使用。
沒名字、無名氏的下場就像免洗餐具一樣,只能用一次。

等於是濃縮了以下幾行程式碼

func main(){
    test()
}

func test(){
    ...
}

【匿名併發】

匿名併發 = 匿名函式加上go併發

https://play.golang.org/p/yjlln9Th00a

func main() {
	go func() { // 把這裡的go併發去掉,就變成匿名函式了
		for i := 0; i < 10000; i++ {
			fmt.Print("匿名併發函式 ")
		}
	}() // 這邊有小括號有點奇怪對不? 因為他是一個函式,在呼叫時需要()

	for i := 0; i < 10000; i++ {
		fmt.Print("main ")
	}

	time.Sleep(10000)
}

【要注意的小坑】

你不一定每次都能期待被你併發出去的func順利執行完任務並且會傳值給你,
人家主線程main func早就打烊、結束營業啦,就算回傳了我也沒辦法接到。
所以說,被go出去的func 沒有return回傳值
因為不能期待他每次都能成功回傳值、不能期待主線程每次都等他們完成。

那要回傳值給main func知道,該怎麼辦呢?
此時就需要自己建立通道(channel)了。
詳見明天的文章。

恐慌(Panic)

恐慌Panic退出程序是什麼關係?

一群地鼠原本在地道挖坑,突然發生恐慌,每隻地鼠抗性不夠、都得了負面狀態,
嚇得趕快逃走,於是就退出程序了。

【Panic】

Panic 是發生了預期之外的事情,導致異常、錯誤的產生,退出程序的同時回傳錯誤代碼2 (Process finished with exit code 2)。我們可以透過panic的func來主動引起錯誤發生。

要注意的是若在併發線程中發生了panic,也會導致主程式也異常結束。

func main() {
	fmt.Println("程式開始")

	go p()
	time.Sleep(time.Second * 1)
	fmt.Println("主程式順利結束")
}

func p() {
	fmt.Println("即將發生空難...")
	panic("空難")
}

/* result:
程式開始
即將發生空難...
panic: 空難

goroutine 18 [running]:
main.p()
	/tmp/sandbox810050981/prog.go:18 +0x95
created by main.main
	/tmp/sandbox810050981/prog.go:11 +0x91
*/

上一篇
# Day13 Go怎麼傳不過去-指標的小用處(Pointer)
下一篇
# Day15 給我Go通道- Golang Channel (Block vs Deadlock)
系列文
Go繁不及備載35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言