前言
Go協程一般使用channel(通道)通信進而協調/同步他們的工作。合理利用Go協程和channel能幫助我們大大提高程式的性能。本文将介紹一些使用channel的場景及技巧
場景一,使用channel傳回運算結果
計算斐波那契數列,在學習遞歸時候這是個經典問題。現在我們不用遞歸實作,而是用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()逾時設定可能引發的記憶體洩露問題,可以看這篇文章
逾時後使用context取消goroutine執行的方法,參考:https://mp.weixin.qq.com/s/780-KicWIQZNwmAtTAHmaw
場景四,多任務并發執行和順序執行
方法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協程都阻塞了,進入了死鎖狀态
總結
本文介紹了幾種場景下channel的使用技巧,希望能起到抛磚引玉的作用,各位如有其它技巧,歡迎評論,本文會把你們的技巧收納在其中。感謝!!!
看完之後若覺得對自己有幫助,懇請點贊或評論。這是對我最大的鼓勵!