以前在開發winform的程式時,如果這個function要處理n個流程,這時候就會看到畫面上的滑鼠指標變成漏斗,
只能等阿等,明明知道這些子function裡面是沒有先後順序,但也只能一個接著一個執行,在delphi寫上面寫Multithread可是一件苦差事。
接觸到Golang後才知道要寫Multithread的程式有多簡單,只是在golang上面使用的是Concurrency
有關Multithread/Concurrency/Parallelism的差異可以參考這篇文章
名詞釐清
身為貓控,有一張有趣的圖來解釋Concurrency/Parallelism
來源 我愛喵喵
對goroutine的解釋,可以參考這篇文章 Goroutine 讓你用少少的線程, 能接受更多的工作, 但沒說會作比較快
直接用實作來秀goroutine的肌肉吧
package main
import (
"fmt"
"time"
)
func doSomethingA(s string, count int, sleep int) {
for i := 0; i < count; i++ {
time.Sleep(time.Duration(sleep) * time.Second)
fmt.Println("doSomethingA:", s, "loop:", i)
}
}
func doSomethingB(s string, count int, sleep int) {
for i := 0; i < count; i++ {
time.Sleep(time.Duration(sleep) * time.Second)
fmt.Println("doSomethingB:", s, "loop:", i)
}
}
func main() {
doSomethingA("mdfk2020", 3, 2)
doSomethingB("ggininder", 2, 1)
}
執行結果,明顯的,是依序func先後順序執行
doSomethingA: mdfk2020 loop: 0
doSomethingA: mdfk2020 loop: 1
doSomethingA: mdfk2020 loop: 2
doSomethingB: ggininder loop: 0
doSomethingB: ggininder loop: 1
goroutine實作很簡單,只要加個go就可以了
package main
import (
"fmt"
"time"
)
func doSomethingA(s string, count int, sleep int) {
for i := 0; i < count; i++ {
time.Sleep(time.Duration(sleep) * time.Second)
fmt.Println("doSomethingA:", s, "loop:", i)
}
}
func doSomethingB(s string, count int, sleep int) {
for i := 0; i < count; i++ {
time.Sleep(time.Duration(sleep) * time.Second)
fmt.Println("doSomethingB:", s, "loop:", i)
}
}
func main() {
go doSomethingA("mdfk2020", 3, 2)
go doSomethingB("ggininder", 2, 1)
}
執行結果:沒印出任何東西,因為執行goroutine後,主線程會繼續向下執行,所以主線程丟出二個goroutine後就程式結束了,這時候加個sleep語法或是for{}卡住主線程就看到了goroutine的執行情形了
doSomethingB: ggininder loop: 0
doSomethingA: mdfk2020 loop: 0
doSomethingB: ggininder loop: 1
doSomethingA: mdfk2020 loop: 1
doSomethingA: mdfk2020 loop: 2
如果不像用for{}的方式也可以用sync.WaitGroup的方式來達成相同的結果,但是
wg.Add() Goroutine的數量要跟實際執行的一致,不然會有二種情形
1.wg.Add()的數量>Goroutine的數量:會一直卡在wg.Wait()
2.相之,會提早結束Goroutine
package main
import (
"fmt"
"sync"
"time"
)
func doSomethingA(wg *sync.WaitGroup, s string, count int, sleep int) {
defer wg.Done()
for i := 0; i < count; i++ {
time.Sleep(time.Duration(sleep) * time.Second)
fmt.Println("doSomethingA:", s, "loop:", i)
}
}
func doSomethingB(wg *sync.WaitGroup, s string, count int, sleep int) {
defer wg.Done()
for i := 0; i < count; i++ {
time.Sleep(time.Duration(sleep) * time.Second)
fmt.Println("doSomethingB:", s, "loop:", i)
}
}
func main() {
var wg sync.WaitGroup
//定義要等待幾個Goroutine
wg.Add(2)
go doSomethingA(&wg, "mdfk2020", 3, 2)
go doSomethingB(&wg, "ggininder", 2, 1)
//卡住,等待Goroutine完成
wg.Wait()
}
當goroutine遇上Closure會發生什麼事情
沒用goroutine
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
//會順序印出0 1 2 3 4
}
call go func
func main() {
var wg sync.WaitGroup
wg.Add(5)
var gg = func(wg *sync.WaitGroup, i int) {
defer wg.Done()
fmt.Println(i)
}
for i := 0; i < 5; i++ {
go gg(&wg, i)
}
wg.Wait()
//會出現隨機數字
}
巨大雷coming~列出來的數字很大機會變成 4 4 4 4 4,
因為變數i 在closure中印出來,所以外層的loop還在一直+1中,
內層的goroutine使用的i也會跟著變動,其實vscode會給提示說這樣子會有問題,但是不會噴錯就是了
提示訊息:loop variable i captured by func literalloopclosure
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
fmt.Println(i)
wg.Done()
}()
}
wg.Wait()
}
如果想用loop+closure的方式跑goroutine可以在closure外重新宣告一個變數去接原本的i
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
//這樣子做就可以避免掉問題
j := i
go func() {
fmt.Println(j)
wg.Done()
}()
}
wg.Wait()
}