我一直在尝试从更传统的语言(如 Java 和 C)进入 Go,到目前为止,我一直很享受 Go 提供的深思熟虑的设计选择。但是,当我开始我的第一个“真正的”项目时,我遇到了一个几乎没有人遇到过的问题。
我的项目是一个发送和接收数据包的简单网络实现。一般结构是这样的(当然是简化的):
客户端管理与服务器的net.Conn
。此 Client
创建一个 PacketReader
和一个 PacketWriter
。它们都在不同的 goroutine 中运行无限循环。 PacketReader
接受一个接口(interface),其中包含一个由客户端实现的 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
填充的 channel ,如下所示:
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 会暂停当前正在解码字节的读取器 goroutine,然后挂起。描述了一个与我的问题相似的问题 here .我已经确认它实际上是 GC 引起的,因为使用 GOGC=off
运行它会成功运行。
此时,我的 3 个例程如下所示:
- 客户端(主例程)
:WAITING channel
- Writer
: 仍在运行,等待队列中的新数据
- Reader
:设置为可运行,但实际上并未运行
不知何故,GC 要么无法停止所有例程以便运行,要么在停止后不会恢复所述 goroutines。
所以我的问题是:有什么方法可以解决这个问题吗?我是 Go 的新手,所以我真的不知道我的设计选择是否非常传统,而且我完全改变我的程序结构。我是否需要更改处理数据包读取回调的方式,是否需要尝试降低数据包解码器的强度?谢谢!
编辑:我正在运行 Go 1.5.1,我将在今天晚些时候尝试获得一个工作示例。
最佳答案
根据 mrd0ll4r s 评论,将作者更改为使用 channel 而不是 slice (我什至不知道为什么我首先这样做)。这似乎为 GC 提供了足够的“机动性”以允许线程停止。添加 runtime.Gosched()
并仍然使用 slice 也可以,但 channel 似乎更“go-esque”。
关于去 GC 停止我的 goroutine?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33364138/