去 GC 停止我的 goroutine?
Go GC stopping my goroutine?
我一直在尝试从 Java 和 C 等更传统的语言进入 Go,到目前为止,我一直在享受 Go 提供的深思熟虑的设计选择。但是,当我开始我的第一个 "real" 项目时,我 运行 遇到了一个几乎没有人遇到过的问题。
我的项目是一个发送和接收数据包的简单网络实现。大体结构是这样的(当然简化了):
客户端通过服务器管理net.Conn
。这 Client
创建一个 PacketReader
和一个 PacketWriter
。这两个 运行 在不同的 goroutine 中无限循环。 PacketReader
接受一个由客户端实现的带有单个 OnPacketReceived
函数的接口。
PacketReader
代码如下所示:
go func() {
for {
bytes, err := reader.ReadBytes(10) // Blocks the current routine until bytes are available.
if err != nil {
panic(err) // Handle error
}
reader.handler.OnPacketReceived(reader.parseBytes(bytes))
}
}()
PacketWriter
代码如下所示:
go func() {
for {
if len(reader.packetQueue) > 0 {
// Write packet
}
}
}()
为了使 Client
阻塞,客户端创建了一个由 OnPacketReceived
填充的通道,如下所示:
type Client struct {
callbacks map[int]chan interface{}
// More fields
}
func (c *Client) OnPacketReceived(packet *Packet) {
c.callbacks[packet.Id] <- packet.Data
}
func (c *Client) SendDataBlocking(id int, data interface{}) interface{} {
c.PacketWriter.QueuePacket(data)
return <-c.callbacks[id]
}
现在这是我的问题:reader.parseBytes
函数执行一些密集的解码操作,创建了相当多的对象(GC 决定 运行)。 然而,GC 暂停了当前正在解码字节的 reader goroutine,然后挂起 。 here 描述了一个看起来与我相似的问题。我已经确认它实际上是 GC 引起的,因为 运行 与 GOGC=off
运行s 成功地结合了它。
此时,我的 3 个例程如下所示:
- Client (main routine)
:等待频道
- Writer
: 还在运行ning,等待队列中的新数据
- Reader
: 设置为运行nable,但实际上不是运行ning
不知何故,GC 要么无法停止所有例程以 运行,要么它在停止 goroutine 后不会恢复它们。
所以我的问题是:有什么方法可以解决这个问题吗?我是 Go 的新手,所以我真的不知道我的设计选择是否非常传统,我完全同意改变我的程序结构。我是否需要更改处理数据包读取回调的方式,是否需要尝试降低数据包解码器的强度?谢谢!
编辑:我正在 运行ning Go 1.5.1,我会在今天晚些时候尝试获得一个工作示例。
根据 mrd0ll4r 的评论,将作者更改为使用通道而不是切片(我什至不知道我一开始为什么这样做)。这似乎给了 GC 足够的 "mobility" 来允许线程停止。添加 runtime.Gosched()
并仍然使用切片也有效,但通道似乎更多 "go-esque".
我一直在尝试从 Java 和 C 等更传统的语言进入 Go,到目前为止,我一直在享受 Go 提供的深思熟虑的设计选择。但是,当我开始我的第一个 "real" 项目时,我 运行 遇到了一个几乎没有人遇到过的问题。
我的项目是一个发送和接收数据包的简单网络实现。大体结构是这样的(当然简化了):
客户端通过服务器管理net.Conn
。这 Client
创建一个 PacketReader
和一个 PacketWriter
。这两个 运行 在不同的 goroutine 中无限循环。 PacketReader
接受一个由客户端实现的带有单个 OnPacketReceived
函数的接口。
PacketReader
代码如下所示:
go func() {
for {
bytes, err := reader.ReadBytes(10) // Blocks the current routine until bytes are available.
if err != nil {
panic(err) // Handle error
}
reader.handler.OnPacketReceived(reader.parseBytes(bytes))
}
}()
PacketWriter
代码如下所示:
go func() {
for {
if len(reader.packetQueue) > 0 {
// Write packet
}
}
}()
为了使 Client
阻塞,客户端创建了一个由 OnPacketReceived
填充的通道,如下所示:
type Client struct {
callbacks map[int]chan interface{}
// More fields
}
func (c *Client) OnPacketReceived(packet *Packet) {
c.callbacks[packet.Id] <- packet.Data
}
func (c *Client) SendDataBlocking(id int, data interface{}) interface{} {
c.PacketWriter.QueuePacket(data)
return <-c.callbacks[id]
}
现在这是我的问题:reader.parseBytes
函数执行一些密集的解码操作,创建了相当多的对象(GC 决定 运行)。 然而,GC 暂停了当前正在解码字节的 reader goroutine,然后挂起 。 here 描述了一个看起来与我相似的问题。我已经确认它实际上是 GC 引起的,因为 运行 与 GOGC=off
运行s 成功地结合了它。
此时,我的 3 个例程如下所示:
- Client (main routine)
:等待频道
- Writer
: 还在运行ning,等待队列中的新数据
- Reader
: 设置为运行nable,但实际上不是运行ning
不知何故,GC 要么无法停止所有例程以 运行,要么它在停止 goroutine 后不会恢复它们。
所以我的问题是:有什么方法可以解决这个问题吗?我是 Go 的新手,所以我真的不知道我的设计选择是否非常传统,我完全同意改变我的程序结构。我是否需要更改处理数据包读取回调的方式,是否需要尝试降低数据包解码器的强度?谢谢!
编辑:我正在 运行ning Go 1.5.1,我会在今天晚些时候尝试获得一个工作示例。
根据 mrd0ll4r 的评论,将作者更改为使用通道而不是切片(我什至不知道我一开始为什么这样做)。这似乎给了 GC 足够的 "mobility" 来允许线程停止。添加 runtime.Gosched()
并仍然使用切片也有效,但通道似乎更多 "go-esque".