深入讨论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. rails4 中使用分页的方法

    以前老版本的rails中默认自带分页方法,不过从rails2.0开始就将内置的分页pagination对象移除了,改以第三方gem提供支持.要在新的rails里使用分页也是非常简单啦,首先安装will ...

  2. c# http请求ajax页面

    我们在用Http请求的时候,某些页面是ajax加载的,所以请求过来的页面数据不完整.也就是说ajax局部加载数据的地方,我们请求不到,这时候该怎么办呢? WebDriver+phantomjs 这两个 ...

  3. 多台或者集群环境下如何保证spring定时器只执行一个

    先说一下我们的系统, 在65和66上分别部署有weblogic节点,共计四个,在项目中我们的定时器会隔一段时间就从其它的五个系统中取数据,这时就出现了问题,本来取一次数据就可以的,现在重复执行了三次, ...

  4. [转]用python 10min手写一个简易的实时内存监控系统

    简易的内存监控系统 本文需要有一定的python和前端基础,如果没基础的,请关注我后续的基础教程系列博客 文章github源地址,还可以看到具体的代码,喜欢请在原链接右上角加个star 腾讯视频链接 ...

  5. python argparse用法总结

    转:python argparse用法总结 1. argparse介绍 argparse是python的一个命令行解析包,非常适合用来编写可读性非常好的程序. 2. 基本用法 prog.py是我在li ...

  6. JSF-页面导航

    页面导航 1)导航处理涉及的术语: -动作值:触发动作事件的组件的action:EL方法表达式.字符串文字. -结果值:动作组件的action属性的:EL方法表达式的返回值.字符串文字:或结果组件的o ...

  7. spring+spring mvc+mybatis 实现主从数据库配置

    一.配置文件 1.jdbc.properties master_driverUrl=jdbc:mysql://localhost:3306/shiro?useUnicode=true&char ...

  8. JavaScript高级程序设计(二)

    一.函数 1.1 JS中函数无重载,同一作用域下定义两个函数,而不会引发错误,但真正调用的是后面定义的函数.例如: function doAdd(iNum){ alert(iNum+100); } f ...

  9. Java 面试知识点解析(二)——高并发编程篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  10. Linux的安装(虚拟机环境)与基础配置

    一.背景 本文介绍如何安装虚拟机VMware以及如果在虚拟机上安装Linux系统以及Linux安装完毕之后的基础配置 需要准备的东西有VMware以及Linux镜像文件 二.下载安装VMware 下载 ...