Channel使用技巧
前言
Go協程一般使用channel(通道)通信從而協調/同步他們的工作。合理利用Go協程和channel能幫助我們大大提高程序的性能。本文將介紹一些使用channel的場景及技巧
場景一,使用channel返回運算結果
計算斐波那契數列,在學習遞歸時候這是個經典問題。現在我們不用遞歸實現,而是用channel返回計算得出的斐波那契數列。 計算前40個斐波那契數列的值,看下效率
package mainimport ("fmt""time" ) //計算斐波那契數列并寫到ch中 func fibonacci(n int, ch chan<- int) {first, second := 1, 1for i := 0; i < n; i++ {ch <- firstfirst, second = second, first+second}close(ch) }func main() {ch := make(chan int, 40)i := 0start := time.Now()go fibonacci(cap(ch), ch)for result := range ch {fmt.Printf("fibonacci(%d) is: %d\n", i, result)i++}end := time.Now()delta := end.Sub(start)fmt.Printf("took the time: %s\n", delta) }只花了7ms,效率是遞歸實現的100倍(主要是算法效率問題)
fibonacci(33) is: 5702887 fibonacci(34) is: 9227465 fibonacci(35) is: 14930352 fibonacci(36) is: 24157817 fibonacci(37) is: 39088169 fibonacci(38) is: 63245986 fibonacci(39) is: 102334155 took the time: 8.0004ms使用for-range讀取channel返回的結果十分便利。當channel關閉且沒有數據時,for循環會自動退出,無需主動監測channel是否關閉。close(ch)只針對寫數據到channel起作用,意思是close(ch)后,ch中不能再寫數據,但不影響從ch中讀數據
場景二,使用channel獲取多個并行方法中的一個結果
假設程序從多個復制的數據庫同時讀取。只需要接收首先到達的一個答案,Query 函數獲取數據庫的連接切片并請求。并行請求每一個數據庫并返回收到的第一個響應:
func Query(conns []conn, query string) Result {ch := make(chan Result, 1)for _, conn := range conns {go func(c Conn) {select {case ch <- c.DoQuery(query):}}(conn)}return <- ch }場景三,響應超時處理
在調用遠程方法的時候,存在超時可能,超時后返回超時提示
func CallWithTimeOut(timeout time.Duration) (int, error) {select {case resp := <-Call():return resp, nilcase <-time.After(timeout):return -1, errors.New("timeout")} }func Call() <-chan int {outCh := make(chan int)go func() {//調用遠程方法}()return outCh }同樣可以擴展到channel的讀寫操作
func ReadWithTimeOut(ch <-chan int) (x int, err error) {select {case x = <-ch:return x, nilcase <-time.After(time.Second):return 0, errors.New("read time out")} } func WriteWithTimeOut(ch chan<- int, x int) (err error) {select {case ch <- x:return nilcase <-time.After(time.Second):return errors.New("read time out")} }使用<-time.After()超時設置可能引發的內存泄露問題,可以看這篇文章
場景四,多任務并發執行和順序執行
方法A和B同時執行,方法C等待方法A執行完后才能執行,main等待A、B、C執行完才退出
package mainimport ("fmt""time" )func B(quit chan<- string) {fmt.Println("B crraied out")quit <- "B" }func A(quit chan<- string, finished chan<- bool) {// 模擬耗時任務time.Sleep(time.Second * 1)fmt.Println("A crraied out")finished <- truequit <- "A" }func C(quit chan<- string, finished <-chan bool) {// 在A沒有執行完之前,finished獲取不到數據,會阻塞<-finishedfmt.Println("C crraied out")quit <- "C" }func main() {finished := make(chan bool)defer close(finished)quit := make(chan string)defer close(quit)go A(quit, finished)go B(quit)go C(quit, finished)fmt.Println(<-quit)fmt.Println(<-quit)fmt.Println(<-quit) }正常執行我們得到以下結果
B crraied out B A crraied out A C crraied out C注意:最后從quit中讀數據不能使用for-range語法,不然程序會出現死鎖
for res := range quit {fmt.Println(res)} fatal error: all goroutines are asleep - deadlock!原因很簡單,程序中quit通道沒有被close,A、B、C運行完了,Go的主協程在for循環中阻塞了,所有Go協程都阻塞了,進入了死鎖狀態
場景五,超時后停止Go協程,避免浪費資源(停止調用鏈)
場景四中,假設A方法掛了或者需要執行很長時間,main協程會等到所有方法執行完才會退出。在實際應用中顯然不行,所以要設置超時時間。問題來了,C方法是基于A方法執行完后才執行的,我們怎樣通知C方法退出呢。這里針對普通的Go協程,不是Http請求,有關Http超時問題引起的內存泄露可以看這篇文章
下面我們修改場景四的代碼,讓A方法有超時設置,C方法在A方法超時后也退出
運行結果
B crraied out B C Exited C timeout A timeout A crraied outA方法用time.Sleep(time.Second * 3)模擬超時任務,代碼最后讓main協程休眠,主要為了說明雖然A超時了,但正常情況下它還是會把任務執行下去的。如果有哪位大俠有什么方法能讓它不執行,還請告知!!!
總結
本文介紹了幾種場景下channel的使用技巧,希望能起到拋磚引玉的作用,各位如有其它技巧,歡迎評論,本文會把你們的技巧收納在其中。感謝!!!
轉載于:https://www.cnblogs.com/FireworksEasyCool/p/11587220.html
總結
以上是生活随笔為你收集整理的Channel使用技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于并行附加特征提取网络的SSD地面小目
- 下一篇: 论文学习20-End-to-end Se