Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/go/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在具有for循环的多个goroutine之间进行通信,其中一个goroutine内部有阻塞函数调用_Go_Design Patterns_Concurrency_Channel - Fatal编程技术网

如何在具有for循环的多个goroutine之间进行通信,其中一个goroutine内部有阻塞函数调用

如何在具有for循环的多个goroutine之间进行通信,其中一个goroutine内部有阻塞函数调用,go,design-patterns,concurrency,channel,Go,Design Patterns,Concurrency,Channel,我正在编写一个Go应用程序,它接受websocket连接,然后启动: listengoroutine,它在连接上侦听客户端消息,并根据接收到的消息通过通道向updateClient发送客户端响应 updateClientgoroutine,用于写入连接 processExternalDatagoroutine从消息队列接收数据,通过通道将数据发送到updateClient,以便updateClient可以使用数据更新客户端 我正在使用库进行websocket连接,它的调用被阻塞。此外,its和r

我正在编写一个Go应用程序,它接受websocket连接,然后启动:

  • listen
    goroutine,它在连接上侦听客户端消息,并根据接收到的消息通过通道向
    updateClient
    发送客户端响应
  • updateClient
    goroutine,用于写入连接
  • processExternalData
    goroutine从消息队列接收数据,通过通道将数据发送到
    updateClient
    ,以便
    updateClient
    可以使用数据更新客户端
  • 我正在使用库进行websocket连接,它的调用被阻塞。此外,its和read方法都会并发调用,这是我使用
    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")
    }