51

### Channel使用技巧

 4 years ago
source link: https://www.tuicool.com/articles/3YJZJzY
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

原文地址

前言

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的使用技巧,希望能起到抛砖引玉的作用,各位如有其它技巧,欢迎评论,本文会把你们的技巧收纳在其中。感谢!!!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK