go 語言併發簡單總結(一)

Go 語言併發簡單總結(一)

——《go語言併發之道》

約束

  1. 特殊限制
    通過公約實現約束,例如團隊規章或者代碼庫規範設置
  2. 詞法約束
    將數據的讀寫處理暴露給需要它的併發進程,例如,使用使用閉包將某個channel的寫入操作約束起來。

for-select循環

for{//無限循環或者使用range
	select{
	//使用channel進行作業
	}
}	

建議的使用場景:

  1. 向channel發送迭代變量
  2. 循環等待停止

防止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=λWL=\lambda * W

其中,L是系統中平均負載數, λ\lambda是負載的平均到達率,W是負載在系統中花費的平均時間。
如果我們在系統中增加隊列,本質上是在增加L。那麼要麼是增大了λ\lambda——nL=nλWnL=n\lambda * W,要麼是增大了W——nL=λnWnL=\lambda * nW,並不會減少負載在系統中的花費時間。
但是,利特爾法則不能預知的情況是處理請求的失敗。某些情況下,隊列的存在是必要的。

context包

理解Go Context機制
Go Context的使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章