前言
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")
- }
- }
场景四, 多任务并发执行和顺序执行
方法 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)
- }
注意: 最后从 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 的使用技巧, 希望能起到抛砖引玉的作用, 各位如有其它技巧, 欢迎评论, 本文会把你们的技巧收纳在其中. 感谢!!!
来源: https://www.cnblogs.com/FireworksEasyCool/p/11587220.html