Golang的通道技巧

1,112 阅读3分钟

计时器及系统负荷均衡

  • time包中的 time.Ticker结构体,该对象以指定的时间间隔重复向结构体中的通道C发送时间值,通道C对用户只读,该对象可通过工厂函数 time.NewTicker(dur int64) 创建,dur是指定的时间间隔,单位为纳秒(ns)。在使协程周期性执行任务(打印状态日志,输出等)时使用。调用Stop() 使计时器停止,与 select结合如下:
  • time.Tick()函数声明为 Tick(d Duration) <-chan Time,该函数返回的通道不需要关闭,它以 d为周期给返回的通道发送时间,d是纳秒数。可以通过此函数限制处理频率,如果应对的请求不平稳,可以增加一个带缓冲的可读写通道,从chRate中读取处理时钟,在请求暴增时可以快速处理与缓冲数相等的请求,之后处理速度会下降到和chRate 一样的速率。

  • 定时器(Timer)定时器和计时器(Ticker)结构体类似(构造函数为 NewTimer(d Duration)),但它只发送一次时间,在 Dration d之后。

  • time.After(d) 函数声明为 func After(d Duration) <-chan Time,在 Duration d 之后,当前时间被发到返回的通道;因此它和NewTimer(d).C等价;它类似 Tick(),但 After() 只发送一次时间。可以使用此函数应对简单的超时模式,以下为三种形式。

    • 要执行某个任务(如从通道 ch 中读取数据),但最多等待1秒。先创建一个信号通道,之后启动一个lambda 协程,协程在给通道发送数据前休眠:

    • time.After()函数替换 timeout-channel。可以在 select 中使用来让发送信号超时或停止协程的执行。以下代码,在 timeoutNs 纳秒后执行 selecttimeout分支后,包含client.Calllambda协程也随之结束,不会给通道ch 返回值。缓冲大小设置为 1 是必要的,可以避免协程死锁以及确保超时的通道可以被垃圾回收。需要注意如果 select 中的某些非定时器选项的通道读写密集,则可能无法结束这些进程。这种情况如果将select 放到一个for 循环中,也无法精确地在定时器通道写入时就结束,因为select对可以执行的多个 case 采取伪随机算法选择,可能结束进程的时间要比定时器发出信号略晚一些。

    • 假设程序从多个复制的数据库同时读取,只需要接收首先到达的答案,Query 函数获取数据库的连接切片,并行请求每一个数据库并返回收到的第一个响应。结果通道 ch 必须是带缓冲的,以保证第一个发送进来的数据有地方可以存放,确保放入的首个数据总会成功:

缓冲通道实现信号量模式

使用缓冲通道模拟信号量,需满足

  • 带缓冲通道的容量和要同步的资源容量相同

  • 通道的长度(当前存放的元素个数)与当前资源被使用的数量相同

  • 容量减去通道的长度就是未处理的资源个数(标准信号量的整数值)

  • 创建一个可缓冲通道表示单一信号量。

  • 将可用资源的数量N来初始化信号量 semaphore:sem = make(semaphore, N),提供方法从信号量通道中读取、写入。

  • 一个互斥的例子

管道过滤

  • 从通道接收的数据并发送给输出通道,可过滤符合条件的数据。

协程的恢复

  • 以下代码停掉了服务器内部一个失败的协程而不影响其他协程的工作。