如何优雅地关闭频道?

时间:2015-01-26 11:57:43

标签: go channel

我有一台服务器,可以像这样处理连接Session

type Session struct {
    conn *net.TCPConn //the tcp connection from client
    recvChan      chan []byte
    closeNotiChan chan bool 
    ok   bool
    lock sync.Mutex
}

func (sess *Session) Close() {
    sess.conn.Close()
    sess.lock.Lock()
    if sess.ok {
        sess.ok = false
        close(sess.closeNotiChan)
    }
    sess.lock.Unlock()
}

func (sess *Session) handleRecv() {
    defer func() {
        sess.Close()
    }()
    ch := sess.recvChan
    header := make([]byte, 2) 
    for {
        /**block until recieve len(header)**/
        n, err := io.ReadFull(sess.conn, header)
        if n == 0 && err == io.EOF {
            //Opposite socket is closed
            log.Warn("Socket Read EOF And Close", sess)
            break
        } else if err != nil {
            //Sth wrong with this socket
            log.Warn("Socket Wrong:", err)
            break
        }
        //body lenght
        size := binary.LittleEndian.Uint16(header) + 4

        data := make([]byte, size)

        n, err = io.ReadFull(sess.conn, t.Data)
        if n == 0 && err == io.EOF {
            log.Warn("Socket Read EOF And Close", sess)
            break
        } else if err != nil {
            log.Warn("Socket Wrong:", err)
            break
        }
        ch <- t //#1
    }
}

func (sess *Session) handleDispatch() {
    defer func() {
        sess.Close()
        close(sess.recvChan)//#2
        for msg := range sess.recvChan {
            //do something 
        }
    }()
    for {
        select {
        case msg, ok := <-sess.recvChan:
            if ok {
                //do something
            }
        case <-sess.closeNotiChan:
            return
        }
    }
}

func StartClient() {//deal a new connection 
    var client Session
    client.conn = connection
    client.recvChan = make(chan []byte, 64)
    client.closeNotiChan = make(chan bool)
    client.ok = true
    go client.handleRecv()
    client.handleDispatch()
}

接收数据和调度数据在不同的goroutines.Now我有一个问题。当连接关闭时,会出现数据竞争,#1#2,如下所示(我不发布完整代码)

WARNING: DATA RACE
Write by goroutine 179:
  runtime.closechan()
      /usr/local/go/src/runtime/chan.go:257 +0x0
  sanguo/gameserver.func·004()
      /data/mygo/src/sanguo/gameserver/session.go:149 +0xc7
  sanguo/gameserver.(*Session).handleDispatch()
      /data/mygo/src/sanguo/gameserver/session.go:173 +0x413
  sanguo/gameserver.(*Session).Start()
      /data/mygo/src/sanguo/gameserver/session.go:223 +0xc6
  sanguo/gameserver.handleClient()
      /data/mygo/src/sanguo/gameserver/gameserver.go:79 +0x43

Previous read by goroutine 126:
  runtime.chansend()
      /usr/local/go/src/runtime/chan.go:83 +0x0
  sanguo/gameserver.(*Session).handleRecv()
      /data/mygo/src/sanguo/gameserver/session.go:140 +0x1171

所以,我的问题是如何在没有“数据竞争”和recvChan的情况下,如何有效地关闭panic

1 个答案:

答案 0 :(得分:0)

频道通过发送同步goroutines,但close上的handleDispatch本身并未同步。如果您在另一个goroutine通过它发送内容的同时关闭一个频道怎么办?

您应该有另一个频道sess.recvChan通知&#34;所有者&#34; ({1}}的作者(即作者),即handleRecv,想要关闭频道。然后handleRecv应该停止发送并关闭它。

除此之外:我不理解#2旁边的行:for msg := range sess.recvChan {如果您刚刚关闭它,您打算从sess.recvChan到达那里? / p>