Golang实现协程超时控制的方式总结

 更新时间:2023年05月24日 10:31:49   作者:tracy小猫  
我们知道,go协程如果不做好处理,很容易造成内存泄漏,所以对goroutine做超时控制,才能有效避免这种情况发生,本文为大家整理了两个常见的Golang超时控制方法,需要的可以收藏一下

Golang对协程做超时的控制大概有两种方式,timer 和context。对于并发情况,又有不同的方式。

我们知道,go协程如果不做好处理,很容易造成内存泄漏。特别是在高并发的情况下,如果某一个 goroutine 由于意外退出,则会导致接收方一直阻塞,从而挂起主程序。

对goroutine做超时控制,能够有效避免这种情况发生。

Timer+Select

time.After 实现超时控制

  • 利用 time.After 启动了一个异步的定时器,返回一个 channel,当超过指定的时间后,该 channel 将会接受到信号。
  • 启动了子协程,函数执行结束后,将向 channel ch 发送结束信号。
  • 使用 select 阻塞等待 done 或 time.After 的信息,若超时,输出timeout,若没有超时,则输出done。
func TestContext12(t *testing.T) {
    ch := make(chan struct{}, 1)
    go func() {
       fmt.Println("running...")
       time.Sleep(3 * time.Second)
       ch <- struct{}{}
   }()
    select {
       case <-ch:
       fmt.Println("done")
       case <-time.After(2 * time.Second):
       fmt.Println("timeout")
   }
}

output:

=== RUN   TestContext12
running...
timeout
--- PASS: TestContext12 (3.01s)
PASS

这里要注意一个点,就是你创建ch 的时候,必须是带缓冲的。如果不带,在并发的情况下会怎样呢?

NewTimer

也可以新建一个NewTimer,timer.C返回也是一个channel

func TestContext5(t *testing.T) {
   timer := time.NewTimer(time.Duration(time.Millisecond * 900))
   ch := make(chan struct{}, 1)
   go func() {
      fmt.Println("running...")
      time.Sleep(3 * time.Second)
      ch <- struct{}{}
   }()
   select {
   case <-ch:
      fmt.Println("done")
   case <-timer.C:
      fmt.Println("timeout")
   }
}

output:

=== RUN   TestContext5
running...
timeout
--- PASS: TestContext5 (0.90s)
PASS

留一个坑

为什么不需要关闭ch 管道?什么时候关闭ch?

Context+Select

  • 第二种方案是利用 context,context.WithTimeout。
  • 它接受一个Context和一个超时时间作为参数,返回一个子Context和一个取消函数CancelFunc。
  • 取消函数CancelFunc将释放子Context与之有关的资源,因此在子Context中的相关操作一旦完成,应该立即调用取消函数CancelFunc。
func TestContext6(t *testing.T) {
   ch := make(chan string)
   timeout, cancel := context.WithTimeout(context.Background(), 2*time.Second)
   defer cancel()
   go func() {
      time.Sleep(time.Second * 3)
      ch <- "done"
   }()
   select {
   case res := <-ch:
      fmt.Println(res)
   case <-timeout.Done():
      fmt.Println("timout", timeout.Err())
   }
}
  • 利用 context.WithTimeou 返回启动了一个异步的定时器,返回一个 channel,当超过指定的时间后,该 channel 将会接受到信号。
  • 启动了子协程,函数执行结束后,将向 channel ch 发送结束信号。
  • 使用 select 阻塞等待 done 或 time.Done的信息,若超时,输出timeout,若没有超时,则输出done。

output:

=== RUN   TestContext6
timout context deadline exceeded
--- PASS: TestContext6 (2.00s)
PASS

context+并发协程

并发协程使用WaitGroup阻塞主协程保证生产协程和消费协程正常执行完成。

func TestContext3(t *testing.T) {
   withTimeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*1)
   waitGroup := sync.WaitGroup{}
   waitGroup.Add(2)
   go func() { //协程1
      time.Sleep(time.Second)
      fmt.Println("finished 1")
      waitGroup.Done()
   }()
   go func() { //协程2
      time.Sleep(time.Second * 2)
      fmt.Println("finished 2")
      waitGroup.Done()
   }()
   go func() {
      select {
      case <-withTimeout.Done():
         fmt.Println("timeout")
         return
      default:
         waitGroup.Wait()
         cancelFunc()
         fmt.Println("finished all")
         return //结束监听协程
      }
   }()
   <-withTimeout.Done()
}
  • 开启协程1,等待1秒输出。协程2,等待2秒输出。
  • context.WithTimeout等待1秒后返回函数。
  • 1秒后,协程一输出。但是协程二没输出,主线程不等待了。直接返回。

output:

=== RUN   TestContext3
finished 1
--- PASS: TestContext3 (1.01s)
PASS

这里多一个问题,留个坑!

goroutine如何超时控制并发输出a,b,c并且按照a,b,c的顺序输出?

到此这篇关于Golang实现协程超时控制的方式总结的文章就介绍到这了,更多相关Golang协程超时控制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang实现AES加密和解密的示例代码

    Golang实现AES加密和解密的示例代码

    AES( advanced encryption standard)使用相同密钥进行加密和解密,也就是对称加密。本文将详细讲解Golang实现AES加密和解密的方法,感兴趣的可以学习一下
    2022-05-05
  • 一文搞懂Golang文件操作增删改查功能(基础篇)

    一文搞懂Golang文件操作增删改查功能(基础篇)

    这篇文章主要介绍了一文搞懂Golang文件操作增删改查功能(基础篇),Golang 可以认为是服务器开发语言发展的趋势之一,特别是在流媒体服务器开发中,已经占有一席之地,今天我们不聊特别深奥的机制和内容,就来聊一聊 Golang 对于文件的基本操作
    2021-04-04
  • go 熔断原理分析与源码解读

    go 熔断原理分析与源码解读

    这篇文章主要为大家介绍了go 熔断原理分析与源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Go语言interface详解

    Go语言interface详解

    这篇文章主要介绍了Go语言interface详解,本文讲解了什么是interface、interface类型、interface值、空interface、interface函数参数等内容,需要的朋友可以参考下
    2014-10-10
  • 详解Golang中interface{}的注意事项

    详解Golang中interface{}的注意事项

    学习 golang ,对于 interface{} 接口类型,我们一定绕不过,这篇文章咱们就来一起来看看 使用 interface{} 的时候,都有哪些注意事项吧
    2023-03-03
  • GoLang实现Viper库的封装流程详解

    GoLang实现Viper库的封装流程详解

    Viper是一个用于Go语言应用程序的配置管理库,它提供了一种简单而灵活的方式来处理应用程序的配置,支持多种格式的配置文件,这篇文章主要介绍了GoLang封装Viper库的流程,感兴趣的同学可以参考下文
    2023-05-05
  • Go语言实现栈与队列基本操作学家

    Go语言实现栈与队列基本操作学家

    go语言中,并没有栈与队列相关的数据结构,但是我们可以借助切片来实现栈与队列的操作;接下来我们一起实现栈与队列基本操作,感兴趣的可以了解一下
    2022-11-11
  • go语言版的ip2long函数实例

    go语言版的ip2long函数实例

    这篇文章主要介绍了go语言版的ip2long函数,实例分析了Go语言实现的ip2long函数技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Go语言中使用urfave/cli命令行框架

    Go语言中使用urfave/cli命令行框架

    这篇文章介绍了Go语言中使用urfave/cli命令行框架的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • Go设计模式之享元模式讲解和代码示例

    Go设计模式之享元模式讲解和代码示例

    享元是一种结构型设计模式,它允许你在消耗少量内存的情况下支持大量对象,模式通过共享多个对象的部分状态来实现上述功能,换句话来说,享元会将不同对象的相同数据进行缓存以节省内存,本文就将通过代码示例给大家详细介绍一下享元模式
    2023-06-06

最新评论