简体   繁体   中英

How can I close the channel elegantly?

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?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM