如何确保在Golang的gorilla WebSocket包中实现并发性?

huangapple go评论75阅读模式
英文:

How to ensure concurrency in Golang gorilla WebSocket package

问题

我已经研究了gorilla/websocket包的Godoc。

在Godoc中明确指出:

并发性
连接支持一个并发的读取器和一个并发的写入器。

应用程序负责确保不超过一个goroutine同时调用写入方法(NextWriter、SetWriteDeadline、WriteMessage、WriteJSON、EnableWriteCompression、SetCompressionLevel),并且不超过一个goroutine同时调用读取方法(NextReader、SetReadDeadline、ReadMessage、ReadJSON、SetPongHandler、SetPingHandler)。

Close和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呢?

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

在处理这个并发问题时是否有更好的方法?提前谢谢。

英文:

I have studied the Godoc of the gorilla/websocket package.

In the Godoc it is clearly stated that

>Concurrency
> Connections support one concurrent reader and one concurrent writer.
>
> Applications are responsible for ensuring that no more than one goroutine calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and that no more than one goroutine calls the read methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) concurrently.
>
> The Close and WriteControl methods can be called concurrently with all other
methods.

However, in one of the example provided by the package

func (c *Conn) readPump() {
    defer func() {
	    hub.unregister &lt;- 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(&quot;error: %v&quot;, err)
		    }
		    break
	    }
	    message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
	    hub.broadcast &lt;- message
    }
}

Source: https://github.com/gorilla/websocket/blob/a68708917c6a4f06314ab4e52493cc61359c9d42/examples/chat/conn.go#L50

This line

c.ws.SetPongHandler(func(string) error { 
    c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil
})

and this line

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

seems to be not synchronized because the first line is a callback function so it should be invoked in a Goroutine created in the package and the second line is executing in the Goroutine that invoke serveWs

More importantly, how should I ensure that no more than one goroutine calls the SetReadDeadline, ReadMessage, SetPongHandler, SetPingHandler concurrently?

I tries to use a Mutex lock and lock it whenever I call the above functions, and unlock it afterwards, but quickly I realize a problem. It is usual (also in the example) that ReadMessage is being called in a for-loop. But if the Mutext is locked before the ReadMessage, then no other Read-functions can acquire the lock and execute until next message is received

Is there any better way in handling this concurrency issue? Thanks in advance.

答案1

得分: 5

确保没有并发调用读取方法的最佳方法是从单个goroutine执行所有的读取方法。

所有的Gorilla websocket示例都使用这种方法,包括在问题中粘贴的示例。在示例中,所有对读取方法的调用都来自readPump方法。readPump方法在单个goroutine上为连接调用一次。因此,连接的读取方法不会并发调用。

文档中关于控制消息的部分指出,应用程序必须读取连接以处理控制消息。根据这一点和Gorilla自己的示例,我认为可以安全地假设ping、pong和close处理程序将从应用程序的读取goroutine中调用,就像当前的实现一样。如果文档能更明确地说明这一点就好了。也许可以提交一个问题?

英文:

The best way to ensure that there are no concurrent calls to the read methods is to execute all of the read methods from a single goroutine.

All of the Gorilla websocket examples use this approach including the example pasted in the question. In the example, all calls to the read methods are from the readPump method. The readPump method is called once for a connection on a single goroutine. It follows that the connection read methods are not called concurrently.

The section of the documentation on control messages says that the application must read the connection to process control messages. Based on this and Gorilla's own examples, I think it's safe to assume that the ping, pong and close handlers will be called from the application's reading goroutine as it is in the current implementation. It would be nice if the documentation could be more explicit about this. Maybe file an issue?

huangapple
  • 本文由 发表于 2017年4月5日 16:04:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/43225340.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定