深入讨论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. C# 如何将PDF转为多种图像文件格式(Png/Bmp/Emf/Tiff)

    PDF是一种在我们日常工作学习中最常用到的文档格式之一,但常常也会因为文档的不易编辑的特点,在遇到需要编辑PDF文档内容或者转换文件格式的情况时让人苦恼.通常对于开发者而言,可选择通过使用组件的方式来 ...

  2. Yii2.0源码阅读-PHP如何与redis通信?

    PHP与Redis可以通过socket进行通信,前提是PHP需要实现Redis的协议 RESP协议描述: 字符串 \r\n : 表示一个正确的状态信息,具体信息是'+'后面的字符(Simple Str ...

  3. 基于 Java Web 的毕业设计选题管理平台--选题报告与需求规格说明书

    一.选题报告 目录 团队名称 团队成员 项目名称 项目描述 创新与收益 用户场景分析 真实用户调研 未来市场与竞争 项目导图 比例权重 总结 1.团队名称--指南者团队 2.团队成员 孔潭活:2015 ...

  4. VMS项目总结

    开发完一个项目后,如果能够很好的对这个项目做个总结,对我们以后的项目开发以及个人技术的积累都会有很大的帮助.最近在外派公司做完一个系统,在此进行一下深入的总结,也希望给读者带来一些个启示. 一.系统介 ...

  5. Linux的硬盘分区

    关于硬盘分区,这里先只讨论硬盘分区的方法在linux环境下,其他知识以后也会陆续说到 首先我们通过 df 命令查看一下硬盘的使用情况,在安装硬盘分区后做比较用 [root@bogon ~]# df - ...

  6. Python 爬取美团酒店信息

    事由:近期和朋友聊天,聊到黄山酒店事情,需要了解一下黄山的酒店情况,然后就想着用python 爬一些数据出来,做个参考 主要思路:通过查找,基本思路清晰,目标明确,仅仅爬取美团莫一地区的酒店信息,不过 ...

  7. TypeScript 的声明文件的使用与编写

    https://fenying.net/2016/09/19/typings-for-typescript/ TypeScript 是 JavaScript 的超集,相比 JavaScript,其最关 ...

  8. 使用Swashbuckle构建RESTful风格文档

    本次和大家分享的是Swagger to WebApi的nuget包Swashbuckle:因为项目需要统一api文档的风格,并要支持多种开发语言(C#,java,python),所以首先想到的是swa ...

  9. python爬虫——词云分析最热门电影《后来的我们》

    1 模块库使用说明 1.1 requests库 requests 是用Python语言编写,基于 urllib,采用 Apache2 Licensed 开源协议的 HTTP 库.它比 urllib 更 ...

  10. java中new关键字和newInstance()方法的区别

    1> new是一个关键字,可以说是一个指令: newInstance()是一个方法,Class对象的一个方法. 2> new主要作用是在内存中生成一个实例,而这个类可以没有提前加载到内从中 ...