前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言中常见100问题-#62 Starting a goroutine without knowing when to ..

Go语言中常见100问题-#62 Starting a goroutine without knowing when to ..

作者头像
数据小冰
发布2022-08-15 15:23:42
3880
发布2022-08-15 15:23:42
举报
文章被收录于专栏:数据小冰
启动一个goroutine但不知道何时停止它

启动一个goroutine是件简单也是件很廉价(占用内存小)的事,以至于我们不太关注何时停止一个goroutine,这可能会导致内存泄露问题。不清楚什么时候停止一个goroutine是一个设计问题,也是Go开发中常见的并发类错误问题。下面开始分析为什么要关注它以及如何防止产生。

首先让我们对一个goroutine泄露产生的影响有一个量的概念。在内存占用方面,goroutine以最小的2KB大小开始分配,可以根据需要增长或缩小,64位系统的最大堆栈为1GB,32位系统的最大堆栈为250MB. goroutine上还可以保存分配给堆的变量引用,保存资源,例如HTTP或DB连接,打开的文件,最终应该正常关闭的网络套接字,如果一个goroutine泄露,这些资源可能也会被泄露。

下面来看一个不清楚什么该停止goroutine运行的例子。程序中,父goroutine调用一个返回通道的函数foo,然后创建一个新的goroutine将从该通道中接收消息。

代码语言:javascript
复制
ch := foo()
go func() {
        for v := range ch {
                // ...
        }
}()

创建的子goroutine将在ch被关闭时退出,但是,我们是否确切知道该通道何时关闭?可能不明显,因为ch是由foo函数创建的,如果通道从未被关闭,那么就会导致泄露。因此,我们应该始终对goroutine的退出点保持谨慎,并确保最终能够退出不会泄露。

现在通过一个具体的例子进行分析说明。我们将设计一个需要监视外部配置的应用程序,例如使用数据库连接,下面是实例代码:

代码语言:javascript
复制
func main() {
        newWatcher()

        // Run the application
}

type watcher struct { /* Some resources */ }

func newWatcher() {
        w := watcher{}
        go w.watch()
}

程序调用newWatcher,它创建了一个watcher结构对象,并启动一个goroutine来负责监视配置变动。这段代码的问题点是当main goroutine退出时(可能是因为操作系统信号或者是有限的工作被处理完),应用程序将停止。这会导致观察者创建的资源不会被优雅地关闭。那我们应该才能防止这种情况产生呢?

一种处理方法是向newWatcher传递一个上下文,该上下文将在main函数返回时被取消,代码如下。

代码语言:javascript
复制
func main() {
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()

        newWatcher(ctx)

        // Run the application
}

func newWatcher(ctx context.Context) {
        w := watcher{}
        go w.watch(ctx)
}

我们将创建的上下文传递给watch方法,当上下文被取消时,观察者应该关闭它的资源,但是,我们能保证观察者有时间完成关闭资源操作吗?我们不能保证,不过这是一个设计的问题。问题的原因是使用信号来传达一个goroutine必须停止,在资源关闭之前,我们没有阻塞父goroutine,下面是一个改进的版本。

代码语言:javascript
复制
func main() {
        w := newWatcher()
        defer w.close()

        // Run the application
}

func newWatcher() watcher {
        w := watcher{}
        go w.watch()
        return w
}

func (w watcher) close() {
        // Close the resources
}

watcher对象有一个close方法,现在不是通过向watcher方法发出信号来关闭它的资源,而是使用defer调用close方法来保证应用程序退出之前资源已经关闭。

总结,我们需要认识到goroutine是一种资源,就像任何其他资源一样,最终必须关闭,无论是释放内存还是其他资源。在不知道何时应该停止goroutine的情况下启动一个goroutine是一个设计问题。每当一个goroutine启动时,我们都应该对它何时停止有一个清晰认识。最后重要的一点,如果一个goroutine创建资源并且它的生命周期与应用程序的生命周期绑定,那么等待它关闭而不是通知它关闭可能更安全,这样可以保证在退出应用程序之前释放资源。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据小冰 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 启动一个goroutine但不知道何时停止它
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档