是併發運算,不是某種併發症。
就是多線程運算。
雖然兩者在口語表達上都會說成『同時進行』,但是中文的『同時』很難區分是併行還是併發
但從英文單字來看的話,會比較容易理解
併發
是共享時間運算
,在一段時間內輪流享有時間資源
併行
是平行運算
,一直都能享有時間資源
併發
是把時間切成很小很小段,在這小段的時間裡先後執行多項任務。
併行
是CPU有多個核心,可以同時處理多個任務。
簡單來說,拿人來比喻的話:
併發
:一個人
在一段時間內做兩件事
併行
:兩個人
同時在做事
Goroutine
是輕量級的線程。
A goroutine is a lightweight thread managed by the Go runtime.
而main func
則是程式當前的、主要的goroutine
。
想要讓Go併發並ㄅㄧㄤˋ叫非常的容易,
只要寫好一個func
,在呼叫時多一個go
關鍵字即可。
多線程讓你的程式嚇嚇叫。
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的併發
會用到多個核心下去執行,試著執行以下的程式看看,
輸出是一段一段的,一下O
一下-
交錯著,代表兩邊的線程都很努力的想噴射把值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(n)
這一參數限制程式執行時 CPU用到的最大核心數量。
如果設置小於1,等於沒設,預設值是電腦核心數。
GOMAXPROCS sets the maximum number of CPUs that can be executing.
Ifn < 1
, it does not change the current setting.
https://play.golang.org/p/idAyE6AsCJ1
但限制了一核心之後,為什麼還是可以把兩個print func
都印到呢,
怎麼不是只印出一個直到時間到?說好的單核心?
原來是Go Routine
會去排程,執行A線程一小段時間後會跳到線程B去,
這才公平合理嘛!不然CPU資源都被其中一個線程給佔住,作業系統就卡死啦。
所以這次看到的輸出會是 O
很多 再來-
很多,兩者都連續印很多的情況下交錯著。
匿名函式(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
是發生了預期之外的事情,導致異常、錯誤的產生,退出程序的同時回傳錯誤代碼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
*/