Go:go 通道和 select 的死锁问题
Go : Deadlock issue with go channel and select
我已经在 golang 中实现了一个演示 tcp 聊天服务器,它工作正常,但每次用户断开连接时,我都会尝试向广播频道写入一条消息,让其他用户知道用户已断开连接,它会阻塞,并且不会进一步处理来自其他客户端的任何新消息,因为它是一个非缓冲通道
我已经按代码注释和解释了你可以看一下,我不知道为什么代码块,我写了消息
- 我要写信给频道
- 我已经写信给频道
- 我已从频道阅读
而且消息井然有序,但我的消息频道仍然阻塞。
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 来执行写入操作。
我已经在 golang 中实现了一个演示 tcp 聊天服务器,它工作正常,但每次用户断开连接时,我都会尝试向广播频道写入一条消息,让其他用户知道用户已断开连接,它会阻塞,并且不会进一步处理来自其他客户端的任何新消息,因为它是一个非缓冲通道
我已经按代码注释和解释了你可以看一下,我不知道为什么代码块,我写了消息
- 我要写信给频道
- 我已经写信给频道
- 我已从频道阅读
而且消息井然有序,但我的消息频道仍然阻塞。
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 来执行写入操作。