---
# System prepended metadata

title: Day14 Go併發症狀- Goroutines (go)

---

# 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-

```go

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

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

/* result:
嚇嚇叫
*/
```


## 【Go併發過程】

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

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

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

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

/* result:
ＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯＯ－－－－－－－－ＯＯ－－－－－－－－－－－ＯＯＯＯＯＯＯＯＯＯＯ...
*/
```


#### 【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`提出去 取一個好名字 再呼叫名字使用。**   
沒名字、無名氏的下場就像免洗餐具一樣，只能用一次。  

等於是濃縮了以下幾行程式碼  
```go
func main(){
    test()
}

func test(){
    ...
}
```

#### 【匿名併發】
`匿名併發` = `匿名函式`加上`go併發`  

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

```go
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`，也會導致主程式也異常結束。  

```go
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
*/
```
