如何确保 Golang Gorilla WebSocket 包中的并发性

8
我研究了gorilla/websocket软件包的Godoc。
在Godoc中,明确说明了:
并发性 连接支持一个并发读取器和一个并发写入器。 应用程序有责任确保不超过一个goroutine同时调用写方法(NextWriter、SetWriteDeadline、WriteMessage、WriteJSON、EnableWriteCompression、SetCompressionLevel),并且不超过一个goroutine同时调用读方法(NextReader、SetReadDeadline、ReadMessage、ReadJSON、SetPongHandler、SetPingHandler)。 关闭和WriteControl方法可以与所有其他方法同时调用。
然而,在该包提供的一个示例中。
func (c *Conn) readPump() {
    defer func() {
        hub.unregister <- c
        c.ws.Close()
    }()
    c.ws.SetReadLimit(maxMessageSize)
    c.ws.SetReadDeadline(time.Now().Add(pongWait))
    c.ws.SetPongHandler(func(string) error { 
        c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil
    })
    for {
        _, message, err := c.ws.ReadMessage()
        if err != nil {
            if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
                log.Printf("error: %v", err)
            }
            break
        }
        message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
        hub.broadcast <- message
    }
}

这行代码来源于:https://github.com/gorilla/websocket/blob/a68708917c6a4f06314ab4e52493cc61359c9d42/examples/chat/conn.go#L50 这一行代码的作用是:
c.ws.SetPongHandler(func(string) error { 
    c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil
})

并且这一行

_, message, err := c.ws.ReadMessage()

第一行是回调函数,所以似乎没有同步,因此应该在包中创建的Goroutine中调用它,第二行在调用serveWs的Goroutine中执行。

更重要的是,我该如何确保不超过一个goroutine并发调用SetReadDeadlineReadMessageSetPongHandlerSetPingHandler?

我尝试使用Mutex锁,在调用上述函数时锁定它,并稍后解锁它,但很快我意识到了一个问题。通常(也在示例中),ReadMessage在for循环中被调用。但如果在ReadMessage之前锁定Mutext,则没有其他Read函数可以获取锁并执行,直到下一条消息被接收。

是否有更好的处理并发问题的方法?提前致谢。


1
在研究了包内部的内容后,如果我没有读错的话,ping和pong回调函数实际上是在调用ReadMessage()的同一个GoRoutine中执行的。因此,该示例仍然保证并发性。而SetPingHandler()和SetPongHandler()也需要进行同步,但在示例中它们在任何ReadMessage()被调用之前就已经被调用了,所以这是可以的。 - user7509214
1个回答

5
确保没有并发调用读取方法的最佳方法是从单个goroutine执行所有读取方法。
所有Gorilla websocket示例都使用这种方法,包括问题中粘贴的示例。在该示例中,所有对读取方法的调用都来自于readPump方法。 readPump方法只为单个goroutine上的连接调用一次。因此,连接读取方法不会并发调用。
文档中关于控制消息的部分表示应用程序必须读取连接以处理控制消息。基于此和Gorilla自己的示例,我认为可以安全地假设ping、pong和close处理程序将从应用程序的读取goroutine中调用,就像当前实现中一样。如果文档能更明确地说明这一点就好了。也许可以提交一个问题?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接