Gorilla Websocket 示例在处理另一个通道的同时尝试将数据发送到通道时挂起?
Gorilla Websocket example hangs when trying to send data to a channel whilst handling another channel?
我正在关注 gorilla websocket 库的聊天 client/server 示例。
https://github.com/gorilla/websocket/blob/master/examples/chat/hub.go#L36
我尝试修改代码以在新客户端连接时通知其他客户端,如下所示:
for {
select {
case client := <-h.register:
h.clients[client] = true
// My addition. Hangs after this (no further register/unregister events are processed):
h.broadcast <- []byte("Another client connected!")
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
我的理解是外for
循环的下一次迭代,广播频道应该接收数据并遵循message
情况下的逻辑,但它只是挂起。
为什么?我找不到任何理由。没有进一步的通道事件被处理(register/unregister 或广播上没有任何事件),这让我认为它是某种无缓冲的通道机制,但我不明白如何?
你的通道是无缓冲的,这意味着每个 read/write 阻塞,直到另一个 goroutine 在同一个通道上执行相反的操作。
当您尝试写入 h.broadcast
时,goroutine 停止,等待 reader。但是同一个 goroutine 应该充当这个通道的 reader,这永远不会发生,因为 goroutine 被写入阻塞了。于是程序死锁。
是的,这行不通。您不能 send/receive 在同一个 go 例程中的同一个无缓冲通道上。
行 h.broadcast <- []byte("Another client connected!")
阻塞,直到另一个 go 例程从队列中弹出。一个简单的解决方案是使 broadcast
通道具有长度为 1 的缓冲区。 broadcast := make(chan []byte, 1)
你可以在这个playground例子中看到它
// c := make(chan int) <- This will hang
c := make(chan int, 1)
c <- 1
fmt.Println(<-c)
解除长度为1的缓冲区和整个系统的死锁。您可以 运行 解决的一个问题是,如果 2 个客户端同时注册,那么您可能会遇到 2 个项目试图被塞入 broadcast
频道的情况,我们又回到了无缓冲通道也有同样的问题。你可以避免这种情况并像这样保持 1 go 例行程序:
for {
select {
case message := <-h.broadcast:
// ...
default:
}
select { // This select statement can only add 1 item to broadcast at most
case client := <-h.register:
// ...
h.broadcast <- []byte("Another client connected!")
}
}
}
但是,如果另一个 go 例程也添加到 broadcast
频道,这仍然会中断。所以我会选择 Cerise Limon 的解决方案,或者对通道进行足够的缓冲,以使其他 go 例程永远不会填充缓冲区。
集线器的 broadcast
通道是无缓冲的。无缓冲通道上的通信等待就绪的发送方和就绪的接收方。 hub goroutine阻塞是因为goroutine不能同时准备好发送和接收。
将频道从无缓冲频道更改为有缓冲频道并不能解决问题。考虑缓冲容量为1的情况:
return &Hub{
broadcast: make(chan []byte, 1),
...
}
这个时间轴:
1 clientA: client.hub.register <- client
2 clientB: c.hub.broadcast <- message
3 hub: case client := <-h.register:
4 hub: h.broadcast <- []byte("Another client connected!")
集线器在 #4 阻塞,因为通道在 #2 已满。将频道容量增加到两个或更多并不能解决问题,因为任何数量的客户端都可以在另一个客户端注册时广播消息。
要解决此问题,请将广播代码移至一个函数并从 select 中的两种情况调用该函数:
// sendAll sends message to all registered clients.
// This method must only be called by Hub.run.
func (h *Hub) sendAll(message []byte) {
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
func (h *Hub) run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
h.sendAll([]byte("Another client connected!"))
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
case message := <-h.broadcast:
h.sendAll(message)
}
}
}
我正在关注 gorilla websocket 库的聊天 client/server 示例。
https://github.com/gorilla/websocket/blob/master/examples/chat/hub.go#L36
我尝试修改代码以在新客户端连接时通知其他客户端,如下所示:
for {
select {
case client := <-h.register:
h.clients[client] = true
// My addition. Hangs after this (no further register/unregister events are processed):
h.broadcast <- []byte("Another client connected!")
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
我的理解是外for
循环的下一次迭代,广播频道应该接收数据并遵循message
情况下的逻辑,但它只是挂起。
为什么?我找不到任何理由。没有进一步的通道事件被处理(register/unregister 或广播上没有任何事件),这让我认为它是某种无缓冲的通道机制,但我不明白如何?
你的通道是无缓冲的,这意味着每个 read/write 阻塞,直到另一个 goroutine 在同一个通道上执行相反的操作。
当您尝试写入 h.broadcast
时,goroutine 停止,等待 reader。但是同一个 goroutine 应该充当这个通道的 reader,这永远不会发生,因为 goroutine 被写入阻塞了。于是程序死锁。
是的,这行不通。您不能 send/receive 在同一个 go 例程中的同一个无缓冲通道上。
行 h.broadcast <- []byte("Another client connected!")
阻塞,直到另一个 go 例程从队列中弹出。一个简单的解决方案是使 broadcast
通道具有长度为 1 的缓冲区。 broadcast := make(chan []byte, 1)
你可以在这个playground例子中看到它
// c := make(chan int) <- This will hang
c := make(chan int, 1)
c <- 1
fmt.Println(<-c)
解除长度为1的缓冲区和整个系统的死锁。您可以 运行 解决的一个问题是,如果 2 个客户端同时注册,那么您可能会遇到 2 个项目试图被塞入 broadcast
频道的情况,我们又回到了无缓冲通道也有同样的问题。你可以避免这种情况并像这样保持 1 go 例行程序:
for {
select {
case message := <-h.broadcast:
// ...
default:
}
select { // This select statement can only add 1 item to broadcast at most
case client := <-h.register:
// ...
h.broadcast <- []byte("Another client connected!")
}
}
}
但是,如果另一个 go 例程也添加到 broadcast
频道,这仍然会中断。所以我会选择 Cerise Limon 的解决方案,或者对通道进行足够的缓冲,以使其他 go 例程永远不会填充缓冲区。
集线器的 broadcast
通道是无缓冲的。无缓冲通道上的通信等待就绪的发送方和就绪的接收方。 hub goroutine阻塞是因为goroutine不能同时准备好发送和接收。
将频道从无缓冲频道更改为有缓冲频道并不能解决问题。考虑缓冲容量为1的情况:
return &Hub{
broadcast: make(chan []byte, 1),
...
}
这个时间轴:
1 clientA: client.hub.register <- client
2 clientB: c.hub.broadcast <- message
3 hub: case client := <-h.register:
4 hub: h.broadcast <- []byte("Another client connected!")
集线器在 #4 阻塞,因为通道在 #2 已满。将频道容量增加到两个或更多并不能解决问题,因为任何数量的客户端都可以在另一个客户端注册时广播消息。
要解决此问题,请将广播代码移至一个函数并从 select 中的两种情况调用该函数:
// sendAll sends message to all registered clients.
// This method must only be called by Hub.run.
func (h *Hub) sendAll(message []byte) {
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
func (h *Hub) run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
h.sendAll([]byte("Another client connected!"))
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
case message := <-h.broadcast:
h.sendAll(message)
}
}
}