Golang中time.After的使用理解与释放问题

Golang中的time.After的使用理解

关于在goroutine中使用time.After的理解, 新手在学习过程中的“此时此刻”的理解,错误还请指正。

先线上代码:

package main

import (
 "fmt"
 "time"
)

func main() {
 //closeChannel()
 c := make(chan int)
 timeout := time.After(time.Second * 2) //
 t1 := time.NewTimer(time.Second * 3) // 效果相同 只执行一次
 var i int
 go func() {
 for {
 select {
 case <-c:
 fmt.Println("channel sign")
 return
 case <-t1.C: // 代码段2
 fmt.Println("3s定时任务")
 case <-timeout: // 代码段1
 i++
 fmt.Println(i, "2s定时输出")
 case <-time.After(time.Second * 4): // 代码段3
 fmt.Println("4s timeout。。。。")
 default:    // 代码段4
 fmt.Println("default")
 time.Sleep(time.Second * 1)
 }
 }
 }()
 time.Sleep(time.Second * 6)
 close(c)
 time.Sleep(time.Second * 2)
 fmt.Println("main退出")
}

主要有以上4点是我们平时遇到的。

首先遇到的问题是:

如上的代码情况下, 代码段3处的case 永远不执行, 无论main进程执行多久。这是为什么呢?

首先我们分析为啥不执行代码段3, 而是程序一直执行的是default.  由此我们判断:

case <- time.After(time.Second)  :

是本次监听动作的超时时间, 意思就说,只有在本次select 操作中会有效, 再次select 又会重新开始计时(从当前时间+4秒后), 但是有default ,那case 超时操作,肯定执行不到了。

那么问题就简单了我们预先定义了计时操作:

case <- timeout:

在goroutine开始前, 我们记录了时间,在此时间3s之后进行操作。相当于定时任务, 并且只执行一次。 代码段1和代码段2 实现的结果都相同

针对以上问题解决后,我写了一个小案例:

package main

import (
 "fmt"
 "time"
)

//发送者
func sender(c chan int) {
 for i := 0; i < 100; i++ {
 c <- i
 if i >= 5 {
 time.Sleep(time.Second * 7)
 } else {
 time.Sleep(time.Second)
 }
 }
}

func main() {
 c := make(chan int)
 go sender(c)
 timeout := time.After(time.Second * 3)
 for {
 select {
 case d := <-c:
 fmt.Println(d)
 case <-timeout:
 fmt.Println("这是定时操作任务 >>>>>")
 case dd := <-time.After(time.Second * 3):
 fmt.Println(dd, "这是超时*****")
 }

 fmt.Println("for end")
 }
}

执行结果:

要注意的是,虽然执行到i == 6时, 堵塞了,并且执行了超时操作, 但是下次select 依旧去除的是6

因为通道中已经发送了6,如果未取出,程序堵塞。

GOLANG中time.After释放的问题

在谢大群里看到有同学在讨论time.After泄漏的问题,就算时间到了也不会释放,瞬间就惊呆了,忍不住做了试验,结果发现应该没有这么的恐怖的,是有泄漏的风险不过不算是泄漏,先看API的说明:

// After waits for the duration to elapse and then sends the current time
// on the returned channel.
// It is equivalent to NewTimer(d).C.
// The underlying Timer is not recovered by the garbage collector
// until the timer fires. If efficiency is a concern, use NewTimer
// instead and call Timer.Stop if the timer is no longer needed.
func After(d Duration) <-chan Time {
 return NewTimer(d).C
}

提到了一句The underlying Timer is not recovered by the garbage collector,这句挺吓人不会被GC回收,不过后面还有条件until the timer fires,说明fire后是会被回收的,所谓fire就是到时间了,写个例子证明下压压惊:

package main

import "time"

func main() {
 for {
  <- time.After(10 * time.Nanosecond)
 }
}

显示内存稳定在5.3MB,CPU为161%,肯定被GC回收了的。当然如果放在goroutine也是没有问题的,一样会回收:

package main

import "time"

func main() {
 for i := 0; i < 100; i++ {
  go func(){
   for {
    <- time.After(10 * time.Nanosecond)
   }
  }()
 }
 time.Sleep(1 * time.Hour)
}

只是资源消耗会多一点,CPU为422%,内存占用6.4MB。因此:

Remark: time.After(d)在d时间之后就会fire,然后被GC回收,不会造成资源泄漏的。

那么API所说的If efficieny is a concern, user NewTimer instead and call Timer.Stop是什么意思呢?这是因为一般time.After会在select中使用,如果另外的分支跑得更快,那么timer是不会立马释放的(到期后才会释放),比如这种:

select {
 case time.After(3*time.Second):
  return errTimeout
 case packet := packetChannel:
  // process packet.
}

如果packet非常多,那么总是会走到下面的分支,上面的timer不会立刻释放而是在3秒后才能释放,和下面代码一样:

package main

import "time"

func main() {
 for {
  select {
  case <-time.After(3 * time.Second):
  default:
  }
 }
}

这个时候,就相当于会堆积了3秒的timer没有释放而已,会不断的新建和释放timer,内存会稳定在2.8GB,这个当然就不是最好的了,可以主动释放:

package main

import "time"

func main() {
 for {
  t := time.NewTimer(3*time.Second)

  select {
  case <- t.C:
  default:
   t.Stop()
  }
 }
}

这样就不会占用2.8GB内存了,只有5MB左右。因此,总结下这个After的说明:

  • GC肯定会回收time.After的,就在d之后就回收。一般情况下让系统自己回收就好了。
  • 如果有效率问题,应该使用Timer在不需要时主动Stop。大部分时候都不用考虑这个问题的。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Go语言利用time.After实现超时控制的方法详解

    前言 在开始之前,对time.After使用有疑问的朋友们可以看看这篇文章:https://www.jb51.net/article/146063.htm 我们在Golang网络编程中,经常要遇到设置超时的需求,本文就来给大家详细介绍了Go语言利用time.After实现超时控制的相关内容,下面话不多说了,来一起看看详细的介绍吧. 场景: 假设业务中需调用服务接口A,要求超时时间为5秒,那么如何优雅.简洁的实现呢? 我们可以采用select+time.After的方式,十分简单适用的实现. 首先

  • Golang中time.After的使用理解与释放问题

    Golang中的time.After的使用理解 关于在goroutine中使用time.After的理解, 新手在学习过程中的"此时此刻"的理解,错误还请指正. 先线上代码: package main import ( "fmt" "time" ) func main() { //closeChannel() c := make(chan int) timeout := time.After(time.Second * 2) // t1 := t

  • golang中bufio.SplitFunc的深入理解

    前言 bufio模块是golang标准库中的模块之一,主要是实现了一个读写的缓存,用于对数据的读取或者写入操作.该模块在多个涉及io的标准库中被使用,比如http模块中使用buffio来完成网络数据的读写,压缩文件的zip模块利用bufio来操作文件数据的读写等. golang的bufio包里面定以的SplitFunc是一个比较重要也比较难以理解的东西,本文希望通过结合简单的实例介绍SplitFunc的工作原理以及如何实现一个自己的SplitFunc. 一个例子 在bufio包里面定义了一些常用

  • 对Golang中的FORM相关字段理解

    Form 字段 通过调用Request结构体提供的方法,我们可以将URL.Body.或者以上两者的数据提取到该结构体的Form.PostForm和MultipartForm等字段中. (1)调用ParseForm方法或者ParseMultipartForm方法,对请求进行分析 (2)访问相应的字段 事例: package main import ( "net/http" "fmt" ) func process(w http.ResponseWriter, r *h

  • golang中的defer函数理解

    目录 golang的defer 什么是defer 理解defer defer什么时间执行(defer. return.返回值 三者的执行顺序) defer输出的值,就是定义时的值.而不是defer真正执行时的变量值(注意引用情况) 多个defer,执行顺序 defer的函数一定会执行么? panic情况 os.Exit情况 kill情况(Ctrl+C) 参考文献 golang的defer 什么是defer defer的的官方文档:https://golang.org/ref/spec#Defer

  • Golang中的参数传递示例详解

    前言 本文主要给大家介绍了关于Golang参数传递的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 关于参数传递,Golang文档中有这么一句: after they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution. 函数调用参数均为值传递,不是指针传递或引用传递.经测试引申出来,

  • golang中struct和interface的基础使用教程

    前言 本文主要给大家介绍了关于golang中struct和interface的相关内容,是属于golang的基本知识,下面话不多说了,来一起看看详细的介绍吧. struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套:go中的struct类型理解为类,可以定义方法,和函数定义有些许区别:struct类型是值类型. struct定义 type User struct { Name string Age int32 mess string } var user User

  • golang中为什么Response.Body需要被关闭详解

    前言 本文主要介绍了关于golang中Response.Body需要被关闭的相关内容,文中通过示例代码介绍的非常详细,对各位学习或者使用golang具有一定参考学习价值,下面话不多说了,来一起看看详细的介绍吧 Body io.ReadCloser The http Client and Transport guarantee that Body is always non-nil, even on responses without a body or responses with a zero

  • golang中值类型/指针类型的变量区别总结

    前言 值类型:所有像int.float.bool和string这些类型都属于值类型,使用这些类型的变量直接指向存在内存中的值,值类型的变量的值存储在栈中.当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝.可以通过 &i 获取变量 i 的内存地址 指针类型:简单地说go语言的指针类型和C/C++的指针类型用法是一样的,除了出去安全性的考虑,go语言增加了一些限制,包括如下几条: 不同类型的指针不能互相转化,例如*int, int32, 以及int

  • Golang中匿名组合实现伪继承的方法

    "Go语言的面向对象机制与一般语言不同. 它没有类层次结构, 甚至可以说没有类: 仅仅通过组合( 而不是继承) 简单的对象来构建复杂的对象." -- <Go语言圣经> 1.匿名组合 1.1 匿名组合定义 golang中组合语法,就是在一个类中,引入了另一个类,如 type Logger struct{ } type Work struct{ log Logger } type Work2 struct{ log *Logger } func (Logger)Info(v .

  • 在Golang中使用Redis的方法示例

    周五上班的主要任务是在公司老平台上用redis处理一个队列问题,顺便复习了一下redis操作的基础知识,回来后就想着在自己的博客demo里,用redis来优化一些使用场景,学习一下golang开发下redis的使用. Redis简单介绍 简介 关于Redis的讨论,其实在现在的后台开发中已经是个老生常谈的问题,基本上也是后端开发面试的基本考察点.其中 Redis的背景介绍和细节说明在这里就不赘述.不管怎么介绍,核心在于Redis是一个基于内存的key-value的多数据结构存储,并可以提供持久化

随机推荐