简体   繁体   中英

Go : Deadlock issue with go channel and select

I've implemented a demo tcp chat server in golang, it works fine, but every time a user disconnects and I try to write a message to the broadcast channel to let other users know a user has disconnected it blocks, and would not further process any new messages from other client because its a nonbuffered channel

I've commented by code and explained it you can go through it, I don't know why the code blocks, I've written msgs

  1. I'm about to write to a channel
  2. I've written to the channel
  3. I've read from the channel

and messages are in perfect order still my msg channel blocks.

Ps: If I'm using buffered channel the code is not blocking, but I want to know where is my code getting stuck. I also tried running my code with -race flag but no help

package main

import (
    "fmt"
    "io"
    "net"
    "sync"
)

func main() {
    msg := make(chan string)          //broadcast channel (making it buffered channel the problem goes away)
    allConn := make(map[net.Conn]int) //Collection of incoming connections for broadcasting the message
    disConn := make(chan net.Conn)    //client disconnect channel
    newConn := make(chan net.Conn)    //new client connection channel
    mutext := new(sync.RWMutex)       //mux to assign unique id to incoming connections
    i := 0
    listener, err := net.Listen("tcp", "127.0.0.1:8081")
    checkErr(err)
    fmt.Println("Tcp server started at 127.0.0.1:8081")
    //Accept incoming connections and store them in global connection store allConn
    go func() {
        for {
            conn, err := listener.Accept()
            checkErr(err)
            mutext.Lock()
            allConn[conn] = i
            i++
            mutext.Unlock()
            newConn <- conn
        }
    }()
    for {
        select {
        //Wait for a new client message to arrive and broadcast the message
        case umsg := <-msg:
            fmt.Println("Broadcast Channel: Already Read")
            bmsg := []byte(umsg)
            for conn1, _ := range allConn {
                _, err := conn1.Write(bmsg)
                checkErr(err)
            }

        //Handle client disconnection [disConn]
        case conn := <-disConn:
            mutext.RLock()
            fmt.Println("user disconneting", allConn[conn])
            mutext.RUnlock()
            delete(allConn, conn)
            fmt.Println("Disconnect: About to Write")
            //this call results in deadlock even when channel is empty, buffered channel resolves the issue
            //need to know why
            msg <- fmt.Sprintf("Disconneting", allConn[conn])
            fmt.Println("Disconnect: Already Written")

        //Read client incoming message and put it on broadcasting channel and upon disconnect put on it disConn channel
        case conn := <-newConn:
            go func(conn net.Conn) {
                for {
                    buf := make([]byte, 64)
                    n, err := conn.Read(buf)
                    if err != nil {
                        if err == io.EOF {
                            disConn <- conn
                            break
                        }
                    }
                    fmt.Println("Client: About to Write")
                    msg <- string(buf[0:n])
                    fmt.Println("Client: Already Written")
                }
            }(conn)
            mutext.RLock()
            fmt.Println("User Connected", allConn[conn])
            mutext.RUnlock()
        }
    }
}
func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

In Go, an unbuffered channel is a "synchronisation point". That is, if you have a channel c , and do c <- value , the goroutine blocks until someone is ready to do v = <- c (and the converse holds, receiving from a blocking channel without something to receive blocks until the value is available, but this is possibly less surprising). Specifically, for a blocking channel, the receive completes before the send completes.

Since you only have a single goroutine, it will be unable to loop back to reading from the channel and the write will block until something can read.

You could, in theory, get around this by doing something like: go func() { msg <- fmt.Sprintf("Disconneting", allConn[conn] }() , so essentially spawning a short-lived goroutine to do the write.

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