golang select 机制和超时问题

 更新时间:2022年06月26日 14:23:47   作者:拾荒志  
golang 中的协程使用非常方便,但是协程什么时候结束是一个控制问题,可以用 select 配合使用,这篇文章主要介绍了golang select 机制和超时问题,需要的朋友可以参考下

golang 中的协程使用非常方便,但是协程什么时候结束是一个控制问题,可以用 select 配合使用。

首先声明,golang 使用并不熟悉,本文仅仅是记录使用过程中遇到的一些坑。

子协程和父协程的通信通常用 context 或者 chan。我遇到一个通常的使用场景,在子协程中尝试多次处理,父协程等待一段时间超时,我选择用 chan 实现。我以为 select 和 C++ 中 switch 类似,所以最开始代码类似如下:

for {
    select {
        case <-ctx.Done():
            // process ctx done
        case <-time.After(time.Second * 3):
            // process after
        default:
            // process code
    }
}

测试发现无法实现 timeout,又仔细查看文档,才发现 golang 中 select 另有玄机。废话少说,直接总结要点:

  • select 中的 case 必须是进行 chan 的手法操作,也就是只能在 case 中操作 chan,并且是 非阻塞接收 。
  • select 中的 case 是同时监听的,多个 case 同时操作,并未 switch 中一个个顺序判断。如果多个 case 满足要求,随机执行一个,如果一个没有则阻塞当前的协程(没有 default 情况下)。 很类似 Linux 文件符操作的 select 语义 。
  • 上面说的阻塞是没有 default 的情况下,如果有 default,则执行 default,然后退出 select,也就是不会阻塞当前协程。

回到上述代码,我这个 select 会一直不断的执行 default, time.After 生成的 chan 并不会被阻塞判断,所以根本无法完成我想要的效果。理解了之后重新修改代码:

done := make(char int)
go func(c chan int) {
    for {
        // process code
        if {
            c <- 1
            return
        }
    }
    c <- 0
}(done)

select {
    case <-ctx.Done():
        // process ctx done
    case <-time.After(time.Second * 3):
        // process after
    case <-done:
        // process code
}

开一个新的协程去不断尝试,在外的三个 case 有一个满足,则会执行。但是这里有一个问题非常需要注意: 子协程什么时候退出? 。

因为 gorountine 不能被强制 kill,所以在上述超时的情况下,select 语句执行 case time.After 之后退出, done 这个 chan 已经没有接受方了,因此既没有接受者,又没有缓冲区,结合 chan 的特性,则子协程会一直阻塞无法退出,所以本质上这个实现会导致子协程累积下去,也就是 协程泄露 ,可能会使资源耗尽。

如何避免上述问题呢?一个很简单的想法就是提供缓冲区, done := make(char int, 1) ,这样即使没有接收方,子协程也能完成发送,不会被阻塞。

还要一种办法,上面说了,select 操作 chan,并且可以指定 default,那是不是有思路了呢?

if {
    select {
        case done <- 1:
        default:
            return
    }
}

我们尝试往 chan 中发送,如果发不出去,则就退出,也实现了目的。

最后总结一下,goroutine 泄露的防范条例:

  • 创建 goroutine 时就要想好该 goroutine 该如何结束。
  • 使用 chan 时,要考虑到 chan 阻塞时协程可能的行为。
  • 实现循环语句时注意循环的退出条件,避免死循环。

到此这篇关于golang select 机制和超时的文章就介绍到这了,更多相关golang select 机制和超时内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang中的空接口使用详解

    golang中的空接口使用详解

    这篇文章主要介绍了golang中的空接口使用,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • go引入自建包名报错:package XXX is not in std解决办法

    go引入自建包名报错:package XXX is not in std解决办法

    这篇文章主要给大家介绍了go引入自建包名报错:package XXX is not in std的解决办法,这是在写测试引入包名的时候遇到的错误提示,文中将解决办法介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • Golang变量直接初始化的方法详解

    Golang变量直接初始化的方法详解

    在 Go 语言中,我们常用的数据结构有在Go语言中,你可以初始化不同的数据结构,例如数组、切片、结构体、指针、map等,本文将给大家介绍一下Golang变量直接初始化的方法,需要的朋友可以参考下
    2023-08-08
  • 详解Golang的GC三色标记法

    详解Golang的GC三色标记法

    这篇文章主要为大家介绍了Golang的GC三色标记法,文中有详细的实现过程供大家参考,对大家的学习或工作有一定帮助,感兴趣的可以跟着小编一来看看
    2023-05-05
  • Golang监听日志文件并发送到kafka中

    Golang监听日志文件并发送到kafka中

    这篇文章主要介绍了Golang监听日志文件并发送到kafka中,日志收集项目的准备中,本文主要讲的是利用golang的tail库,监听日志文件的变动,将日志信息发送到kafka中 ,需要的朋友可以参考一下
    2022-04-04
  • Go语言遍历map实现(访问map中的每一个键值对)

    Go语言遍历map实现(访问map中的每一个键值对)

    这篇文章主要介绍了Go语言遍历map实现(访问map中的每一个键值对),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • 解决Golang json序列化字符串时多了\的情况

    解决Golang json序列化字符串时多了\的情况

    这篇文章主要介绍了解决Golang json序列化字符串时多了\的情况,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang中errgroup的常见误用详解

    Golang中errgroup的常见误用详解

    errgroup和sync.WaitGroup类似,都可以发起执行并等待一组协程直到所有协程运行结束,本文主要为大家整理了一些errgroup的常见误用,有需要的可以参考下
    2024-01-01
  • Go语言基础学习之Context的使用详解

    Go语言基础学习之Context的使用详解

    在Go语言中,Context是一个非常重要的概念,它用于在不同的 goroutine 之间传递请求域的相关数据,本文将深入探讨Go语言中 Context特性和Context的高级使用方法,希望对大家有所帮助
    2023-05-05
  • golang 获取当前执行程序路径的操作

    golang 获取当前执行程序路径的操作

    这篇文章主要介绍了golang 获取当前程序执行路径的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12

最新评论