geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
166 stars 21 forks source link

如何退出协程 goroutine (超时场景) | Go 语言高性能编程 | 极客兔兔 #114

Open geektutu opened 3 years ago

geektutu commented 3 years ago

https://geektutu.com/post/hpg-timeout-goroutine.html

Go 语言/golang 高性能编程,Go 语言进阶教程,Go 语言高性能编程(high performance go)。本文介绍了 Go 语言中实现超时(timeout)退出时常见的错误,即子协程(goroutine)不能正常关闭,导致内存泄漏。常见的实现超时的方式有 time.After 和 context.WithTimeout。

zeromake commented 3 years ago

还有一个技巧对于chan只需要做一次信号传递,且没有数据传输可以用 make(chan struct{}) 传递时可以用 close,标准库的 context 就是这么用的。

geektutu commented 3 years ago

@zeromake 嗯,没有数据传输推荐使用空结构体 struct{},空结构体占用内存为 0。

fmt.Println(unsafe.Sizeof(struct{}{}))  // 0
liron-li commented 3 years ago

2.3中的第二段用的无缓冲chan,也没使用select发送消息,为什么还能正常退出协程呢

geektutu commented 3 years ago

@liron-li 因为主协程超时退出,没有 phase1 的接收方,子协程中 case phase1 <- true: 消息发送失败,返回了。

GodXuebi commented 2 years ago

@geektutu @liron-li 因为主协程超时退出,没有 phase1 的接收方,子协程中 case phase1 <- true: 消息发送失败,返回了。

作者这样回答的话,就和你另一段文字(如下)冲突了: 我们仔细阅读这段代码,其实是非常容易发现问题所在的。done 是一个无缓冲区的 channel,如果没有超时,doBadthing 中会向 done 发送信号,select 中会接收 done 的信号,因此 doBadthing 能够正常退出,子协程也能够正常退出。

但是,当超时发生时,select 接收到 time.After 的超时信号就返回了,done 没有了接收方(receiver),而 doBadthing 在执行 1s 后向 done 发送信号,由于没有接收者且无缓存区,发送者(sender)会一直阻塞,导致协程不能退出。

我的理解是,主协程超时退出,没有phase1的接受方,那么子协程case phase1 <- true仍然是阻塞的,但是可以选择select的default case进行执行,从而避免了协程泄漏问题。

kuankuanlv commented 1 year ago

2.1中的做法会不会也导致内存泄漏?因为此时有1000个chan未被close