在我的代码中,安全完成goroutines的正确方法是什么?

在我的代码中,安全完成goroutines的正确方法是什么?,go,channel,goroutine,Go,Channel,Goroutine,我正在编写一个简单的tcp服务器,goroutine模型非常简单: 一个goroutine负责接受新的连接;对于每个新连接,将启动三个goroutine: 一个供阅读 一个用于处理和处理应用程序逻辑 一个用来写字的 目前一台服务器最多只能服务1000个用户,所以我不想限制goroutine的数量 for { conn, err := listener.Accept() // .... connHandler := connHandler{ conn:

我正在编写一个简单的tcp服务器,goroutine模型非常简单:

一个goroutine负责接受新的连接;对于每个新连接,将启动三个goroutine:

  • 一个供阅读
  • 一个用于处理和处理应用程序逻辑
  • 一个用来写字的
  • 目前一台服务器最多只能服务1000个用户,所以我不想限制goroutine的数量

    for {
        conn, err := listener.Accept()
        // ....
        connHandler := connHandler{
            conn:      conn,
            done:      make(chan struct{}),
            readChan:  make(chan string, 100),
            writeChan: make(chan string, 100),
        }
        // ....
        go connHandler.readAll()    
        go connHandler.processAll() 
        go connHandler.writeAll()   
    }
    
    我使用
    done
    频道通知所有三个频道完成,当用户注销或发生永久性网络错误时,
    done
    频道将关闭(使用sync.one确保只关闭一次):

    下面是
    writeAll()
    方法的代码:

    func (connHandler *connHandler) writeAll() {
        writer := bufio.NewWriter(connHandler.conn)
    
        for {
            select {
            case <-connHandler.done:
                connHandler.conn.Close()
                return
            case msg := <-connHandler.writeChan:
                connHandler.writeOne(msg, writer)
            }
        }
    }
    
    Send
    方法将主要在
    processAll()
    goroutine中调用,但在许多其他goroutine中也会调用,因为不同的用户需要相互通信

    现在的问题是:如果userA注销或网络失败,userB向userA发送消息,userB的goroutine可能会被永久阻止,因为没有人会从通道接收消息

    func (connHandler *connHandler) Send(msg string) {
        select {
        case connHandler.writeChan <- msg:
        case <- connHandler.done:
            log.Debug("connHandler is done, exiting Send without sending.")
        case <-time.After(10 * time.Second):
            log.Warning(connHandler.Addr() + " send msg timeout:" + msg)
        }
    }
    
    我的解决方案:

    我的第一个想法是使用布尔值来确保connHanler在发送到它时没有关闭:

    func (connHandler *connHandler) Send(msg string) {
        if !connHandler.isClosed {
            connHandler.writeChan <- msg
        }
    }
    
    现在我觉得代码是安全的,但也很难看,每次发送消息时都会启动计时器,这感觉像是不必要的开销

    然后我读了这篇文章:,我的问题看起来像文章中的第二个例子:

    一个接收者,N个发送者,接收者说“请停止发送更多” 通过关闭额外的信号通道

    但我认为,在我的情况下,这种解决方案无法消除阻塞的可能性

    也许最简单的解决方案是关闭写入通道,让
    发送
    方法死机,然后使用
    恢复
    来处理死机?但这看起来也是一种丑陋的方式

    那么,有没有一种简单直接的方法来完成我想做的事情呢


    (我的英语不好,所以如果有任何歧义,请指出,谢谢。)

    你的例子看起来很好,我认为你已经得到了你需要的90%

    我认为你看到的问题是发送,而实际上你可能已经“完成”

    您可以使用“完成”频道通知所有已完成的围棋程序。您将始终能够从闭合通道读取值(它将是零值)。这意味着您可以更新<代码>发送(MSG)< /Cord>方法,以考虑已完成的通道。< /P>
    func (connHandler *connHandler) Send(msg string) {
        select {
        case connHandler.writeChan <- msg:
        case <- connHandler.done:
            log.Debug("connHandler is done, exiting Send without sending.")
        case <-time.After(10 * time.Second):
            log.Warning(connHandler.Addr() + " send msg timeout:" + msg)
        }
    }
    
    func(connHandler*connHandler)发送(消息字符串){
    挑选{
    
    您的
    Send()
    中的case connHandler.writeChan可能您可以
    在接收用户的
    connHandler.done
    上选择
    。我认为使用这里的选择确实解决了我的问题,很简单,但我没有看到它!非常感谢。
    
    func (connHandler *connHandler) Send(msg string) {
        if !connHandler.isClosed {
            timer := time.NewTimer(10 * time.Second)
            defer timer.Stop()
            select {
            case connHandler.writeChan <- msg:
            case <-timer.C:
                log.Warning(connHandler.Addr() + " send msg timeout:" + msg)
            }
        }
    
    }
    
    func (connHandler *connHandler) Send(msg string) {
        select {
        case connHandler.writeChan <- msg:
        case <- connHandler.done:
            log.Debug("connHandler is done, exiting Send without sending.")
        case <-time.After(10 * time.Second):
            log.Warning(connHandler.Addr() + " send msg timeout:" + msg)
        }
    }