Go 語言併發簡單總結(一)
——《go語言併發之道》
約束
- 特殊限制
通過公約實現約束,例如團隊規章或者代碼庫規範設置 - 詞法約束
將數據的讀寫處理暴露給需要它的併發進程,例如,使用使用閉包將某個channel的寫入操作約束起來。
for-select循環
for{//無限循環或者使用range
select{
//使用channel進行作業
}
}
建議的使用場景:
- 向channel發送迭代變量
- 循環等待停止
防止goroutine泄露
爲了能夠及時地通知一個goroutine完成或取消工作,使用一個信號通道進行管理。在父子之間建立一個只讀的channel,父goroutine將該channel傳遞給子goroutine,然後在想取消子goroutine時,關閉該channel
doWork := func(
done <-chan interface{},
){
go func(){
for{//這裏又用到了select循環模式
select{
case <-done:
//done被關閉時,子goroutine結束工作
return
case:
//正常處理工作
}
}
}
}
錯誤處理
type Result struct{
Error error,
//other data or info,
}
通過構建一個包含錯誤信息的結構體,並在併發的子goroutine結束時返回該類型變量,在父進程中檢查error來處理錯誤
pipeline
屬性定義:
- 一個輸入的參數與返回值類型相同的stage
- 一個stage必須通過編程語言實現之後才能被當作參數傳遞
對於Pipeline來說,處理方式分爲流處理和批處理,對比如下:
流處理
multiply := func(value, multilier int) int{
return multilier * value
}
使用流處理,對於內存佔用空間消耗相對少,但是,不得不將pipeline放到循環結構中去,這限制了我們向重複利用的pipeline發送消息,而且對於程序的擴展性也不強。同時,儘管函數調用的代價很低,但是循環的每次迭代進行多次調用,併發性將受到威脅。
批處理
multiply := func(value []int, multilier int) int{
multipliedValues := make([]int, len(values))
for i,v := range values{
multipliedValues = values[i] * multilier
}
return multipliedValues
}
扇入扇出
考慮在多個goroutine上重用pipeline的單個stage以試圖並行化來自上游的stage 的pull,這有助於提高pipeline的性能
在以下兩個條件都成立的情況下,考慮使用扇出模式:
- 不依賴於之前stage計算出的值
- 運行需要很長時間
例如:
numFinders := runtime.NumCPU()
finders := make([]<-chan int, numFinders)
for i:= 0; i<numFinders; i++{
finders[i] = primeFinder(done,randIntStream)
}
primeFinder是素數篩選函數
啓動該stage的多個副本,這裏副本數量由CPU核心數決定,實際使用過程中,可以採用一些經驗性的測試確定CPU的最佳數量。
現在,我們開啓了四個子goroutine,對應的輸出了四個不同的channel,爲了綜合結果,使用扇入模式,將多個數據流複用或者合併成一個流
fanIn := func(
done <-chan interface{},
channels ...<-chan interface{},
) <-chan interface{}{
var wg sync.WaitGroup
multiplexedStream := make(chan interface{})
multiplex := func(c<-chan interface){
defer wg.Done()
for i:=range c{
select{
case <-done:
return
case multiplexedStream <-i:
}
}
}
wg.Add(len(channels))
for _,c:=range channels{
go multiplex(c)
}
go func(){
wg.Wait()
close(multiplexedStream)
}()
return multiplexedStream
}
or-done-channel
操作來自系統各個部分的channel時,無法做出斷言,因此得進行一些判斷,在數量較多時,將會帶來不必要的工作,此時,可以用一個goroutine解決這個問題,將我們關心的數據重點暴露出來,封裝其他不必要部分。
orDone := func(done, c<- chan interface{}) <-chan interface{}{
varStream := make(chan interface{})
go func(){
defer close(varStream)
for{
select{
case <-done:
return
case var,ok := <-c:
if ok == false{
return
}
select{
case valStream<-c:
case <-done
}
}
}()
return valStream
}
for val := range orDone(done, myChan){
//執行操作
}
tee-channel
傳遞一個讀channel,返回兩個相同值的單獨的channel
實現略
隊列排隊
隊列幾乎不會加速程序運行的總時間,它只能讓程序的行爲有所不同
證明:
根據利特爾法則:
其中,L是系統中平均負載數, 是負載的平均到達率,W是負載在系統中花費的平均時間。
如果我們在系統中增加隊列,本質上是在增加L。那麼要麼是增大了——,要麼是增大了W——,並不會減少負載在系統中的花費時間。
但是,利特爾法則不能預知的情況是處理請求的失敗。某些情況下,隊列的存在是必要的。