# 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 ```go 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 ```go 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. ```go 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)前 只有該線程能對其進行操作。
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up