這三章節內容其實滿少的,讀過之後隨便記一記
ch1
Why Is Concurrency Hard?
Race condition
應該要依序執行的不同指定們,沒有依照正確的順序造成結果錯誤
例如下面這段程式就有可能出現很多結果,因為使用者可能以為go裡面的程式碼寫在前面,就會先跑
|
|
Atomicity
不可切割,安全,一次要做完
Memory Access Synchronization
不同地方可能 concurrently 存取同一個記憶體,需要變成 critical section 用鎖鎖住之類的,但會變慢
Deadlocks, Livelocks, and Starvation
dead lock -> 4個條件
Determining Concurrency Safety
• Who is responsible for the concurrency?
• How is the problem space mapped onto concurrency primitives?
• Who is responsible for the synchronization?
ch2
Go CSP
下圖是 Concurrency in Go 33 頁的圖
primitives 指的像是 sync package 裡面的 mutex
四個條件的解釋
-
Are you trying to transfer ownership of data?
盡量維持一次只有一個人有資料的擁有權利,做完一件事後要把output丟給別人這種動作,適合用 channel -
Are you trying to guard internal state of a struct?
把實作藏起來不讓外面看到 -> 用primitives -
Are you trying to coordinate multiple pieces of logic?
到處都有 lock 很容易造成問題,而 channel 的組合性很好,出錯也好抓,複雜的邏輯最好用 channel 來做 而不是自己到處鎖來鎖去 -
Is it a performance-critical section?
真的發現使用 channel 造成巨大效能問題才改用 primitives,而不是為了效能一開始就用 primitives
ch3
goroutine 不能被 interrupt?
以前好像是,後來改成可以,不然垃圾回收之類的功能有可能被卡住
但大多還是在固定時間才會被暫停,例如 sleep, 等 io 之類的
goroutine closure 在同一個 address space 執行
var wg sync.WaitGroup
salutation := "hello"
wg.Add(1)
go func() {
defer wg.Done()
salutation = "welcome"
}()
wg.Wait()
fmt.Println(salutation)
The sync package
WaitGroup
var wg sync.WaitGroup // 傳進 func 時要傳 pointer
wg.Add(2) // +2
defer wg.Done() // -1
wg.Wait() // == 0 時完成
mutex
var lock sync.Mutex
lock.Lock()
defer lock.Unlock()
var m sync.RWMutex
m.Lock() // rw lock
m.RLock() // r lock
m.RLocker() // r locker
Cond
有效率的等待,不用浪費CPU跑迴圈
c := sync.NewCond(&sync.Mutex{})
c.L.Lock()
for conditionTrue() == false {
c.Wait() // suspend, 其他 goroutine 可以用 thread
}
c.L.Unlock()
c.Singal() 去戳正在 Wait 的一個 goroutine (最早開始等的)
c.Broadcast() 去戳所有在 Wait 的 goroutines
signal 可以用 channel 來做出一樣的功能
但 broadcast 會比較麻煩,也可能需要很多操作
once
var once sync.Once
once.Do(xxx)
即使一堆 goroutine,也保證 once 只會做一件事情
下面這種也是只會執行aaa
once.Do(aaa)
once.Do(bbb)
計數是記once 做過的事情而不是 unique function 數
Pool
創造及持有東西很貴 不希望有太多 所以放在一個 pool 重複運用並限制數量
提前把一些東西塞進 pool 避免 create,像是 db 連線等等
|
|
使用方法:
- 加入 New func
- Get 的東西有可能是任何狀態
- 記得 Put 回去,通常用 defer
- 裡面的東西基本上要長得一樣
Channels
var dataStream chan int
var dataStream <-chan int
var dataStream chan<- int
dataStream = make(<-chan int, 1)
很少會 make 單向的 chan,通常是傳參數時會用
dataStream := make(chan int)
var d chan<- int
d = dataStream
關閉 channel
之後還是可以無限讀取,因為可能很多下游的 goroutine 都需要讀
dataStream := make(chan int)
go func() {
close(dataStream)
}()
d, ok := <-dataStream
fmt.Println("I GET! ", d, ok) // 0, false
收到 close 時會直接 break
for integer := range intStream {
fmt.Printf("%v ", integer)
}
p.75 列出了不同狀態的 chan 上操作會出現什麼情況,包括錯誤們
p. 91 有範例
要用好 channel 有幾點
- 確認 channel 的 owner 是誰(盡量少)
- owner 負責 channel 的創造、寫、關、轉移 ownership
- 非 owner 負責讀,確認 chan 何時關、處理 blocking
例如第 2 點,把 owner 限制在很小的範圍,就可以避免寫入 nil chan、close closed chan 之類的 p.75 所提的程式邏輯 bug
Select
|
|
當多個case都可以時會隨機選一個
timeout
case <-time.After(1 * time.Second):
default (通常和for一起用) 其他 case 都沒中的時候
default: