我一直试图从更传统的语言(如Java和C)进入Go,到目前为止,我一直在享受Go提供的经过深思熟虑的设计选择。当我开始我的第一个“真正的”项目时,我遇到了几乎没有人似乎有的问题。
我的项目是一个简单的网络实现,可以发送和接收数据包。一般结构是这样的(当然简化):
客户端使用服务器管理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会暂停正在解码字节的读取器goroutine,然后挂起。 here描述了与我的相似的问题。我已经确认它实际上是GC导致它,因为运行它GOGC=off
成功运行。
此时,我的3个例程看起来像这样:
- Client (main routine)
:等待频道
- Writer
:仍在运行,等待队列中的新数据
- Reader
:设置为可运行但未实际运行
不知何故,GC要么无法停止所有例程才能运行,要么在停止后不再恢复所述goroutine。
所以我的问题是:有没有办法解决这个问题?我是Go的新手所以我真的不知道我的设计选择是否是远程传统的,我都是改变我的程序结构。我是否需要更改处理数据包读取回调的方式,是否需要尝试使数据包解码器不那么密集?谢谢!
编辑:我正在运行Go 1.5.1,我会在今天晚些时候尝试一个有效的例子。
答案 0 :(得分:1)
根据mrd0ll4r的评论,将作者更改为使用频道而不是片段(我甚至不知道为什么我首先这样做)。这似乎给了GC足够的“移动性”以允许线程停止。添加runtime.Gosched()
并且仍然使用切片也有效,但是频道看起来更像“go-esque”。