如何优雅地关闭频道?

I have a server , which deal the connection Session like this

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()
}

receiving data and dispatching data are in different goroutines.Now I have a problem. When a connection close,there is a data race ,#1 and #2 ,something like the following(I don't post complete code)

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

So, My problem is that how can I close the recvChan elegently and effectively ,without "data race" and panic.

Channels synchronize goroutines via sending, but close on them isn't synchronized itself. What if you close a channel at the same time another goroutine was sending something through it?

You should have another channel through which handleDispatch notifies the "owner" (ie. the writer) of sess.recvChan, namely handleRecv, that it wants to close the channel. Then handleRecv should stop sending from it and close it.

Apart from that: I don't understand the line next to #2: for msg := range sess.recvChan { What are you trying to get there from sess.recvChan, if you just closed it?