Go:go 通道和 select 的死锁问题

Go : Deadlock issue with go channel and select

我已经在 golang 中实现了一个演示 tcp 聊天服务器,它工作正常,但每次用户断开连接时,我都会尝试向广播频道写入一条消息,让其他用户知道用户已断开连接,它会阻塞,并且不会进一步处理来自其他客户端的任何新消息,因为它是一个非缓冲通道

我已经按代码注释和解释了你可以看一下,我不知道为什么代码块,我写了消息

  1. 我要写信给频道
  2. 我已经写信给频道
  3. 我已从频道阅读

而且消息井然有序,但我的消息频道仍然阻塞。

Ps:如果我使用缓冲通道,代码不会阻塞,但我想知道我的代码在哪里卡住了。 我也尝试 运行 我的代码带有 -race 标志但没有帮助

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

在 Go 中,无缓冲通道是 "synchronisation point"。也就是说,如果你有一个通道 c,并且执行 c <- value,goroutine 会阻塞,直到有人准备好执行 v = <- c(反之亦然,从一个阻塞的通道接收而无需任何东西在值可用之前接收块,但这可能并不令人惊讶)。具体来说,对于阻塞通道,接收在发送完成之前完成。

由于您只有一个 goroutine,它将无法循环回从通道读取并且写入将阻塞直到可以读取。

从理论上讲,您可以通过执行以下操作来解决此问题:go func() { msg <- fmt.Sprintf("Disconneting", allConn[conn] }(),因此本质上是生成一个 short-lived goroutine 来执行写入操作。