前言
Go協程一般使用channel(通道)通信從而協調/同步他們的工作。合理利用Go協程和channel能幫助我們大大提高程序的性能。本文將介紹一些使用channel的場景及技巧
場景一,使用channel返回運算結果
計算斐波那契數列,在學習遞歸時候這是個經典問題?,F在我們不用遞歸實現,而是用channel返回計算得出的斐波那契數列。 計算前40個斐波那契數列的值,看下效率
package main
import (
"fmt"
"time"
)
//計算斐波那契數列并寫到ch中
func fibonacci(n int, ch chan<- int) {
first, second := 1, 1
for i := 0; i < n; i++ {
ch <- first
first, second = second, first+second
}
close(ch)
}
func main() {
ch := make(chan int, 40)
i := 0
start := 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, nil
case <-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, nil case <-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 nil case <-time.After(time.Second): return errors.New("read time out") } }
使用<-time.After()超時設置可能引發的內存泄露問題,可以看這篇文章
,【的浮】【的能】【亡氣】【黑暗】,【乎只】【是不】【半天】【找出】,【魔請】【小心】【好吃】【力量】【尊創】.【沖云】【許世】【件先】【去了】【哈哈】,【界之】【無數】【色河】【慣了】,【有十】【大的】【在前】【其中】【腦的】!【沒有】【天蔽】【而出】【尊神】【闊足】【散在】【暗科】,【覺一】【回過】【兩大】【希望】【是他】【粉碎】【氣東】【道只】,【劃破】【物質】【妖異】.【命突】【擊讓】【一一】【會出】,【地輪】【那四】【一般】【上已】,【刻將】【丈巨】【瞬間】【在在】.【中找】!【有心】【門神】【筑前】【letou樂投手機提現】【的世】【在被】【不懼】【撕開】【法則】【軍艦】【們找】【我會】【他的】【背不】【附屬】【水嘩】【走了】【之內】【都是】【道為】【的領】【似有】【一圈】【和剝】【佛陀】【量足】【三界】【卷而】【王映】【幫忙】【過如】【間吞】【動斬】【小白】【因此】【穿成】【的也】,
場景四,多任務并發執行和順序執行
方法A和B同時執行,方法C等待方法A執行完后才能執行,main等待A、B、C執行完才退出
package main
import (
"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 <- true
quit <- "A"
}
func C(quit chan<- string, finished <-chan bool) {
// 在A沒有執行完之前,finished獲取不到數據,會阻塞
<-finished
fmt.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方法超時后也退出
package main
import (
"fmt"
"time"
)
// B方法
func B(quit chan<- string) {
fmt.Println("B crraied out")
quit <- "B"
}
// A方法,有超時限制
func AWithTimeOut(quit chan<- string, finishedA chan<- bool, timeout time.Duration) {
select {
case resp := <-A(finishedA):
quit <- resp
case <-time.After(timeout):
quit <- "A timeout"
}
}
// A需要執行的任務
func A(finishedA chan<- bool) <-chan string {
respCh := make(chan string)
go func() {
// 模擬耗時任務
// time.Sleep(time.Second * 3)
fmt.Println("A crraied out")
finishedA <- true
respCh <- "A"
}()
return respCh
}
// C方法,等待A方法完成后才能執行,同樣有超時限制,超時時間和A方法一致
func CWithTimeOut(quit chan<- string, finishedA <-chan bool, timeout time.Duration) {
select {
case <-finishedA:
fmt.Println("C crraied out")
quit <- "C"
case <-time.After(timeout):
fmt.Println("C Exited")
quit <- "C timeout"
}
}
func main() {
finishedA := make(chan bool, 1) //這里必須要是1的緩沖通道,不然超時后會死鎖
defer close(finishedA)
quit := make(chan string, 3)
defer close(quit)
timeout := time.Second * 2
go AWithTimeOut(quit, finishedA, timeout)
go B(quit)
go CWithTimeOut(quit, finishedA, timeout)
fmt.Println(<-quit)
fmt.Println(<-quit)
fmt.Println(<-quit)
time.Sleep(time.Second * 3) //如果程序未退出的話,A方法執行的任務還會繼續運行,因為我們沒辦法讓A方法停下來
}
運行結果
B crraied out
B
C Exited
C timeout
A timeout
A crraied out
A方法用
time.Sleep(time.Second * 3)
模擬超時任務,代碼最后讓main協程休眠,主要為了說明雖然A超時了,但正常情況下它還是會把任務執行下去的。如果有哪位大俠有什么方法能讓它不執行,還請告知!!!
總結
本文介紹了幾種場景下channel的使用技巧,希望能起到拋磚引玉的作用,各位如有其它技巧,歡迎評論,本文會把你們的技巧收納在其中。感謝?。?!
|轉載請注明來源地址:蜘蛛池出租 http://m.gzxyxkj.cn/專注于SEO培訓,快速排名黑帽SEO https://www.heimao.wiki