如何在具有for循环的多个goroutine之间进行通信,其中一个goroutine内部有阻塞函数调用
我正在编写一个Go应用程序,它接受websocket连接,然后启动:如何在具有for循环的多个goroutine之间进行通信,其中一个goroutine内部有阻塞函数调用,go,design-patterns,concurrency,channel,Go,Design Patterns,Concurrency,Channel,我正在编写一个Go应用程序,它接受websocket连接,然后启动: listengoroutine,它在连接上侦听客户端消息,并根据接收到的消息通过通道向updateClient发送客户端响应 updateClientgoroutine,用于写入连接 processExternalDatagoroutine从消息队列接收数据,通过通道将数据发送到updateClient,以便updateClient可以使用数据更新客户端 我正在使用库进行websocket连接,它的调用被阻塞。此外,its和r
listen
goroutine,它在连接上侦听客户端消息,并根据接收到的消息通过通道向updateClient
发送客户端响应updateClient
goroutine,用于写入连接processExternalData
goroutine从消息队列接收数据,通过通道将数据发送到updateClient
,以便updateClient
可以使用数据更新客户端updateClient
goroutine的主要原因,goroutine是调用write方法的单一例程
当我需要关闭连接时会出现问题,这至少在两种情况下会发生:
processExternalData
已完成,没有更多数据可更新客户端,应关闭连接updateClient
需要以某种方式通知listen
退出,反之亦然listen
需要以某种方式通知updateClient
退出updateClient
在select
中有一个退出通道,但是listen
不能有select
,因为它已经有一个for
循环,其中包含阻止读取调用
因此,我所做的是在连接类型上添加了isJobFinished
字段,这是循环工作的一个条件:
type WsConnection struct {
connection *websocket.Conn
writeChan chan messageWithCb
quitChan chan bool
isJobFinished bool
userID string
}
func processExternalData() {
// receive data from message queue
// send it to client via writeChan
}
func (conn *WsConnection) listen() {
defer func() {
conn.connection.Close()
conn.quitChan <- true
}()
// keep the loop for communication with client
for !conn.isJobFinished {
_, message, err := conn.connection.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
// convert message to type messageWithCb
switch clientMessage.MessageType {
case userNotFound:
conn.writeChan <- messageWithCb{
message: map[string]interface{}{
"type": user,
"payload": false,
},
}
default:
log.Printf("Unknown message type received: %v", clientMessage)
}
}
log.Println("end of listen")
}
func updateClient(w http.ResponseWriter, req *http.Request) {
upgrader.CheckOrigin = func(req *http.Request) bool {
return true
}
connection, err := upgrader.Upgrade(w, req, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
wsConn := &WsConnection{
connection: connection,
writeChan: make(chan messageWithCb),
quitChan: make(chan bool),
}
go wsConn.listen()
for {
select {
case msg := <-wsConn.writeChan:
err := connection.WriteJSON(msg.message)
if err != nil {
log.Println("connection.WriteJSON error: ", err)
}
if wsConn.isJobFinished {
if msg.callback != nil {
msg.callback() // sends on `wsConn.quitChan` from a goroutine
}
}
case <-wsConn.quitChan:
// clean up
wsConn.connection.Close()
close(wsConn.writeChan)
return
}
}
}
类型WsConnection struct{
连接*websocket.Conn
用CB写下陈消息
基钦·陈布尔
伊斯乔布布尔酒店
用户标识字符串
}
func processExternalData(){
//从消息队列接收数据
//通过writeChan将其发送给客户端
}
func(conn*WsConnection)listen(){
延迟函数(){
conn.connection.Close()
conn.quitChan关闭连接以断开阻止读取调用的listen
goroutine
在updateClient
中,添加一个DEBER语句以关闭连接并清理其他资源。从函数返回任何错误或退出通道的通知:
updateClient(w http.ResponseWriter, req *http.Request) {
upgrader.CheckOrigin = func(req *http.Request) bool {
return true
}
connection, err := upgrader.Upgrade(w, req, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer connection.Close() // <--- Add this line
wsConn := &WsConnection{
connection: connection,
writeChan: make(chan messageWithCb),
quitChan: make(chan bool),
}
defer close(writeChan) // <-- cleanup moved out of loop below.
go wsConn.listen()
for {
select {
case msg := <-wsConn.writeChan:
err := connection.WriteJSON(msg.message)
if err != nil {
log.Println("connection.WriteJSON error: ", err)
return
}
case <-wsConn.quitChan:
return
}
}
}
不需要字段isJobFinished
问题和答案中的代码存在一个问题,即writeChan
的关闭与发送到频道的信号不协调。如果没有看到processExternalData
功能,我无法对该问题的解决方案发表评论
使用互斥体而不是goroutine来限制写入并发性可能是有意义的。同样,需要使用processExternalData
函数中的代码来进一步说明此主题。为什么不能关闭quitChan
?如果一个goroutine关闭它,所有goroutine都会知道它已关闭,然后它们可以终止。您可以可以将isJobFinished
替换为quitChan
谢谢,使用defer
并关闭updateClient
的连接是一个很好的提示。此外,还可以打开processExternalData
,因为它还可以访问writeChan
。
func (conn *WsConnection) listen() {
defer func() {
conn.connection.Close()
close(conn.quitChan) // <-- close instead of sending value
}()
// keep the loop for communication with client
for {
_, message, err := conn.connection.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
// convert message to type messageWithCb
switch clientMessage.MessageType {
case userNotFound:
conn.writeChan <- messageWithCb{
message: map[string]interface{}{
"type": user,
"payload": false,
},
}
default:
log.Printf("Unknown message type received: %v", clientMessage)
}
}
log.Println("end of listen")
}