iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 16
2
Software Development

Go繁不及備載系列 第 16

# Day16 我OK,你先GO (sync)

Day16 我OK,你先GO (sync)

昨天提到了Go併發中通道Channel交換訊息的方法,但世界總是沒有想像中的美好。
有些被併發出去的func做的事情少,很快就回來了,有些則慢吞吞...
要怎麼知道他們到底完事了沒有?

等待集合

【sync.WaitGroup】

可以透過內建的sync WaitGroup來等待線程結束,
就像一群新兵在準備集合,等到每個人都到為止。

A WaitGroup waits for a collection of goroutines to finish.

以下是班長與兩位班兵的角色:

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

func main() {
	fmt.Println("你各位啊,現在開始休息,三秒鐘後記得回來。")

	wg := sync.WaitGroup{} // 也可以var wg = sync.WaitGroup{},或者不要實體化 var wg sync.WaitGroup 
	wg.Add(2)	// 總共有兩位新兵

	go rest(&wg)
	go rest(&wg)

	fmt.Println("===你各位再慢慢來沒關係啊===")
	wg.Wait()
	fmt.Println("===集合完畢===")
}

func rest(wg *sync.WaitGroup) {
	time.Sleep(time.Second * 3)
	fmt.Println("新兵休息完畢。")
	wg.Done()	// 跑去集合
}

/* result:
你各位啊,現在開始休息,三秒鐘後記得回來。
===你各位再慢慢來沒關係啊===
新兵休息完畢。
新兵休息完畢。
===集合完畢===
*/

WaitGroup拿計數器(Counter)來當作任務數量,若counter < 0會發生panic

  • WaitGroup.Add(n):計數器+n
  • WaitGroup.Done():任務完成,從計數器中減去1,可搭配defer使用
  • WaitGroup.Wait():阻塞(Block)住,直到計數器歸0

如果計數器大於線程數就會發生死結(Deadlock)
啊兵就只有兩隻,等到死還是只有這麼多隻,永遠沒辦法集合完畢。

因為是針對該鎖的物件操作,記得是要傳入func指針(Pointer)位址(Address)


看起來方便好用,貌似能解決一切的問題。
但不負所望地,世界還是沒有你想像的美好,...

競爭危害(Race Condition)

【爭奪變數】

在這個例子中我使用了10000被併發出去的func
每個func只做一件事:count++

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

var count = 0

func main() {
	// runtime.GOMAXPROCS(1)  // 只讓一個線程運作就能解決問題。但...想要更快,就是要多核心嘛!單核心怎能星爆?
	for i := 0; i < 10000; i++ {
		go race()
	}
	time.Sleep(time.Millisecond * 100)
	fmt.Println(count)
}

func race() {
	count++
}

/* result:
9763
*/

什麼?輸出居然不是10000

但如果數字小一點,例如10,就又恢復正常了?

這就是爭奪資源的關係。
物競天擇,在這凡事都講求時效性的年代,手腳比較快的就容易成功弄破碗。

多個CPU在搶奪count這個變數,好比多個男性在追求一個異性:

同時有兩個男性問:「小姊,請問您單身嗎?」
『是的,我目前單身哦。』

於是她同時和兩位男性交往。

該如何對付呢?

互斥鎖(Lock)

會面遇到的上面問題,是由於同時對變數進行讀寫(Read/Write)的關係,
在小故事中則是同時
被問與答

同時有兩位男性警察詢問:「小姊,請問您單身嗎?」
『是的,我目前單身哦。』

此時...
由於員警A學過互斥鎖的原理,知道這裡若慢了一步,一切就完了
於是以迅雷不及掩耳之勢拿出手銬腳鐐,把愛慕對象鎖住帶回偵辦

員警B沒有好好念書,只能原地傻傻乾等,悔恨莫及..

【sync.Mutex】

If the lock is already in use, the calling goroutine blocks until the mutex is available.

var count = 0
var m sync.Mutex

func main() {
	for i := 0; i < 10000; i++ {
		go race()
	}
	time.Sleep(time.Millisecond * 100)
	fmt.Println(count)
}

func race() {
	m.Lock()
	count++
	m.Unlock()
}

/* result:
10000
*/

只要在變數前上鎖(Lock),在解鎖(Unlock)前 只有該線程能對其進行操作。


上一篇
# Day15 給我Go通道- Golang Channel (Block vs Deadlock)
下一篇
# Day17 Go-拖延症候群 (defer)
系列文
Go繁不及備載35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
alan_jhu
iT邦新手 5 級 ‧ 2021-06-30 17:20:51

go go go~

我要留言

立即登入留言