深入讨论channel timeout

Go 语言的 channel 本身是不支持 timeout 的,所以一般实现 channel 的读写超时都采用 select,如下:

select {
case <-c:
case <-time.After(time.Second):
}

这两天在写码的过程中突然对这样实现 channel 超时产生了怀疑,这种方式真的好吗?于是我写了这样一个测试程序:

package main

import (
"os"
"time"
) func main() {
c := make(chan int, 100) go func() {
for i := 0; i < 10; i++ {
c <- 1
time.Sleep(time.Second)
} os.Exit(0)
}() for {
select {
case n := <-c:
println(n)
case <-timeAfter(time.Second * 2):
}
}
} func timeAfter(d time.Duration) chan int {
q := make(chan int, 1) time.AfterFunc(d, func() {
q <- 1
println("run") // 重点在这里
}) return q
}

这个程序很简单,你会发现运行结果将会输出 10 次 “run”,也就是每一遍执行 select 注册的 timer 最终都执行了,虽然这里读 channel 都没有超时。原因其实很简单,每次执行 select 语句,都会将 case 条件语句给执行一遍,于是 timeAfter 的执行结果就是会创建一个定时器,并注册到 runtime 中,select 语句执行完成后,这个定时器本身并没有撤销,还继续保留在 runtime 的小顶堆中,所以这些 timer 一超时就会执行挂载的函数。

当然,用 time.After() 函数来做 channel 的读写超时,在应用层根本感受不到底层的定时器还保留着、继续执行;问题是,如果这里的 select 语句在循环中执行得非常快,也就是 channel 中的消息来得非常频繁,会出现的问题就是 runtime 中会有大量的定时器存在,timeout 的时间设置得越长,底层维护的定时器就会越多。原因就是每次 select 都会注册一个新的 timer,并且 timer 只有在它超时后才会被删除。

想想,自己的 channel 每秒钟将传输成千上万的消息,将会有多少 timer 对象存在底层 runtime 中。大量的临时对象会不会影响内存?大量的 timer 会不会影响其他定时器的准确度?

最后,我觉得正确的 channel timeout 也许应该这么做:

to := time.NewTimer(time.Second)
for {
to.Reset(time.Second)
select {
case <-c:
case <-to.C:
}
}

这样做就是为了维护一个全局单一的定时器,每次操作前调整一下定时器的超时时间,从而避免每次循环都生成新的定时器对象。

简单测试了一下两种 channel 超时实现方式,在全力收发数据的情况的内存对象和 gc 情况。 
 
* 蓝线是采用 time.After(),并设置4s 超时的堆内存对象分配的数量 * 绿线是采用 time.After(),并设置2s 超时的堆内存对象分配的数量 * 黄线是采用全局 timer,并设置4s 超时的堆内存对象分配的数量

这个现象其实是预料之中的,重点可以注意设置的超时时间越长,time.After() 的表现将越糟糕。

这三条线和上图的三条线描述的对象是一样的,图中的 gc 时间是平均每次 gc 的时间。

针对这个 channel timeout,我没有去测试是否会影响其他定时器的准确性,但我认为这是必然的,随着定时器的增多。

最后,我始终觉得 channel 本身应该支持超时机制,而不是利用 select 来实现。

另外参见:

如何正确使用 Timer 来完成上面提到的定时任务?

func demo(input chan interface{}) {
t1 := time.NewTimer(time.Second * 5)
t2 := time.NewTimer(time.Second * 10) for {
select {
case msg <- input:
println(msg) case <-t1.C:
println("5s timer")
t1.Reset(time.Second * 5) case <-t2.C:
println("10s timer")
t2.Reset(time.Second * 10)
}
}
}

改正后的程序,原理上是自定义两个全局的 Timer,每次执行 select 都重复使用这两个 Timer,而不是每次都生成全新的。这样才可以真正做到在接收消息的同时,还能够定时的执行相应的任务。


探索任何一个现象背后的真正原因,才是最有趣的事情。

深入讨论channel timeout的更多相关文章

  1. 【转】golang的channel的几种用法

    关闭2次 ch := make(chan bool) close(ch) close(ch)  // 这样会panic的,channel不能close两次 读取的时候channel提前关闭了 ch : ...

  2. Apache Flume 1.7.0 发布,日志服务器

    Apache Flume 1.7.0 发布了,Flume 是一个分布式.可靠和高可用的服务,用于收集.聚合以及移动大量日志数据,使用一个简单灵活的架构,就流数据模型.这是一个可靠.容错的服务. 本次更 ...

  3. Mousejack Hacking : 如何利用MouseJack进行物理攻击

    0×00 前言 近期安全公司Bastille Networks(巴士底狱)安全研究员发现大多数无线鼠标和接收器之间的通信信号是不加密的.黑客可对一百米范围内存在漏洞的蓝牙无线键鼠进行嗅探甚至劫持,从而 ...

  4. The Services(服务)

    datastore和运行时环境的关系就是和一个服务的关系:应用使用API访问一个独立的系统(separate system),这个系统管理应用的所有的独立于应用实例的扩展需求(scaling need ...

  5. yate.conf

    但档案.粘贴下面的例子.不解释!除去非常灵活!只保留sip电话! [general] ; General settings for the operation of Yate ; modload: b ...

  6. 第十三章:Python の 网络编程进阶(二)

    本課主題 SQLAlchemy - Core SQLAlchemy - ORM Paramiko 介紹和操作 上下文操作应用 初探堡垒机 SQLAlchemy - Core 连接 URL 通过 cre ...

  7. Android Wifi 主动扫描 被动扫描

    介绍主动扫描,被动扫描以及连接的wifi的扫描过程 参考文档 <802.11无线网络权威指南> <80_Y0513_1_QCA_WCN36X0_SOFTWARE_ARCHITECTU ...

  8. Golang利用select实现超时机制

    所谓超时,比如上网浏览一些安全的网站,如果几分钟之后不做操作,那么就会让你重新登录.就所谓有时候出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞情况,这时候就可以用select来设置 ...

  9. The Microservices Workflow Automation Cheat Sheet

    Written by Bernd Rücker on Dec 12 2018 in the Best Practices category. Editor’s Note: This post orig ...

随机推荐

  1. python socketserver框架解析

    socketserver框架是一个基本的socket服务器端框架, 使用了threading来处理多个客户端的连接, 使用seletor模块来处理高并发访问, 是值得一看的python 标准库的源码之 ...

  2. ubuntu导入公钥的方法

    导入公钥的办法: #方法1: gpg --keyserver subkeys.pgp.NET --recv 6E871C4A881574DEgpg --export --armor 6E871C4A8 ...

  3. 我的AngularJS学习轨迹

    开始接触Anguljar可能是在2013年初,那个时候的版本1.0.*,那个时候国内主要的中文资料AngularJS学习笔记:http://zouyesheng.com/angular.html,an ...

  4. spring cloud 入门系列五:使用Feign 实现声明式服务调用

    一.Spring Cloud Feign概念引入通过前面的随笔,我们了解如何通过Spring Cloud ribbon进行负责均衡,如何通过Spring Cloud Hystrix进行服务断路保护,两 ...

  5. java中split分割"."的问题

    今天使用split分割"."的时候居然失败了,经过百度发现原来要加转义字符才行. 正确的写法: String test="1.2.3"; String[] s1 ...

  6. nginx基本配置参数说明

    #运行用户 user nobody; #启动进程,通常设置成和cpu的数量相等 worker_processes 1; #全局错误日志及PID文件 #error_log logs/error.log; ...

  7. 微信小程序-框架详解(1)

    配置 -app.json文件对微信小程序进行全局配置,决定页面文件的路径.窗口表现.设置网络超时时间.tab等 { "pages": [ //决定页面文件的路径 "pag ...

  8. SpringMVC SessionAttributes 简述

    使用SpringMVC时,我们会发现网络上有关SessionAttributes注解的内容非常少,更多的人甚至推荐你继续用HttpServletRequest中的session管理方法来控制Sessi ...

  9. cocos2d-x学习之路之工作吐槽

    经过大半年的cocos2d-x的学习,目前已在一个游戏创业公司实习,负责客户端的代码编写和维护.公司做了一款网游.比较给力,马上就要发布了.希望能够大卖.比较坑的是,居然电脑不给联网.查资料都不好查, ...

  10. 【定时器】Quartz初步实验

    第一步:创建项目 创建一个新项目,可以是ASP.NET MVC,WebForms,Winforms等多种.Net项目,这里使用的是VS2017,创建了一个MVC项目 创建完成后大致项目层级为: 第二部 ...