iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
0

以前在開發winform的程式時,如果這個function要處理n個流程,這時候就會看到畫面上的滑鼠指標變成漏斗,
只能等阿等,明明知道這些子function裡面是沒有先後順序,但也只能一個接著一個執行,在delphi寫上面寫Multithread可是一件苦差事。

接觸到Golang後才知道要寫Multithread的程式有多簡單,只是在golang上面使用的是Concurrency
有關Multithread/Concurrency/Parallelism的差異可以參考這篇文章
名詞釐清
身為貓控,有一張有趣的圖來解釋Concurrency/Parallelism
https://ithelp.ithome.com.tw/upload/images/20201003/20129515vYDiPA648y.png
來源 我愛喵喵

goroutine

對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

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

sync.WaitGroup 讓Goroutine等待到全部完成才繼續向下

如果不像用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()
}

上一篇
[DAY25]Golang的實時分佈式消息傳遞平台-NSQ
下一篇
[DAY27]Golang最強大的特性:Goroutine -2
系列文
欸你這週GO了嘛30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言