深入讨论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. rails应用ajax之二:使用rails自身支持

    考虑另一种情况: 1. 页面上半部分显示当前的所有用户,页面下半部分是输入新用户的界面: 2. 每当输入新用户时,页面上半部分会动态更新新加用户的内容: 我们还是用ajax实现,不过这次用rails内 ...

  2. ruby中如何直接编译运行C代码

    我们知道ruby通过C EXT可以很方便的运行C代码,其实只要是C接口的语言都可以,比如汇编语言也可以哦.但是一些简单的函数难道还得费劲用C扩展的方式吗?其实ruby gem里有RubyInline包 ...

  3. Java中使用有返回值的线程

    在创建多线程程序的时候,我们常实现Runnable接口,Runnable没有返回值,要想获得返回值,Java5提供了一个新的接口Callable,可以获取线程中的返回值,但是获取线程的返回值的时候,需 ...

  4. SQL语言逻辑执行顺序

    SQL语言逻辑执行顺序 2012-12-18 16:18:13 分类: 数据库开发技术 查询的逻辑执行顺序 FROM < left_table> ON < join_conditio ...

  5. [更新]单线程的JS引擎与 Event Loop

    先来思考一个问题,JS 是单线程的还是多线程的?如果是单线程,为什么JavaScript能让AJAX异步发送和回调请求,还有setTimeout也看起来像是多线程的?还有non-blocking IO ...

  6. Day20 Ajax

    Ajax准备知识:json 什么是json? 定义: JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式.它基于 ECMAScript (w ...

  7. 8.1 Socket编程

    8.1 Socket编程 在很多底层网络应用开发者的眼里一切编程都是Socket,话虽然有点夸张,但却也几乎如此了,现在的网络编程几乎都是用Socket来编程.你想过这些情景么?我们每天打开浏览器浏览 ...

  8. 线上Django项目python2到3升级日记

    这两天干了一个几斤疯狂的事情,花不到一个工作日的时间把一个线上Django项目语言版本从python2升级到Python31.字典的一个语法变化 Python2.7: if dict1.haskey( ...

  9. kaggle入门项目:Titanic存亡预测(三)数据可视化与统计分析

    ---恢复内容开始--- 原kaggle比赛地址:https://www.kaggle.com/c/titanic 原kernel地址:A Data Science Framework: To Ach ...

  10. 团队项目第二阶段个人进展——Day4

    一.昨天工作总结 冲刺第四天,分析完成后端处理的数据有主题,时间,地点,照片信息,前几个都是字符串类型,后一个是照片格式 二.遇到的问题 照片格式数据不知道怎么处理 三.今日工作规划 学习后端小程序后 ...