英文:
Golang non blocking channel doesn't work
问题
我第一次在Go语言中使用goroutines和channels,并且无法进一步进行。
我有一个websocket连接,每当用户连接时,就会生成一个新的goroutine。现在,如果用户从websocket连接断开,我想停止这个goroutine。
为了管理停止信号,我创建了一个通道映射。每个条目可以通过用户的websocket连接进行标识。我将websocket连接、停止信号的通道映射和其他两个参数传递给goroutine。但是,goroutine没有从quit通道接收任何值,我不知道为什么。
这是main
包的相关代码:
package main
import (
"net/http"
"time"
"github.com/gorilla/websocket"
)
func wsHandler(w http.ResponseWriter, r *http.Request) {
...
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer ws.Close()
data.Quit[ws] = make(chan bool)
data.DB.ListenToTable(data.GetTableName(source), channel, data.Quit, ws)
for {
if _, _, err := ws.NextReader(); err != nil {
data.Quit[ws] <- true
ws.Close()
break
}
}
}
以及data
包中创建goroutine的代码:
package data
var Quit = make(map[*websocket.Conn](chan bool))
func (db *rethinkDB) ListenToTable(name string, ch chan Data, quit map[*websocket.Conn](chan bool), ws *websocket.Conn) {
go func(name string, ws *websocket.Conn) {
for {
select {
case <-quit[ws]:
fmt.Println("QUIT GOROUTINE")
break
default:
res, err := r.Table(name).Changes().Run(db.session)
if err != nil {
log.Fatalln(err)
}
var response DataFeed
for res.Next(&response) {
response.NewVal.WS = ws
ch <- response.NewVal
}
if res.Err() != nil {
log.Println(res.Err())
}
}
}
}(name, ws)
}
我还尝试过使用带缓冲的通道或将通道传递给goroutine而不是通道映射,但都没有成功。fmt.Println("QUIT GOROUTINE")
命令从未被调用,goroutine也没有停止。
希望有人能帮助我,如果这个问题已经被提出过,对不起,我没有找到解决我的问题的解决方案。
英文:
I work the first time with goroutines and channels in go and do not come any further.
I have a websocket connection where every time a user connect a new goroutine is spawned. Now I want to stop this goroutine if the user disconnect from the websocket connection.
To manage the stop signale I have create a map of channels. Each entry can be identified by the users websocket connection. I pass the websocket connection, the map of channels for the stop signal and two other parameters to the goroutine. But the goroutine doesn't receive any values from the quit channel and I don't know why.
Here is the relevant code for the main
package:
package main
import (
"net/http"
"time"
"github.com/gorilla/websocket"
)
func wsHandler(w http.ResponseWriter, r *http.Request) {
...
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer ws.Close()
data.Quit[ws] = make(chan bool)
data.DB.ListenToTable(data.GetTableName(source), channel, data.Quit, ws)
for {
if _, _, err := ws.NextReader(); err != nil {
data.Quit[ws] <- true
ws.Close()
break
}
}
}
And the code of the data
package where the goroutine is created:
package data
var Quit = make(map[*websocket.Conn](chan bool))
func (db *rethinkDB) ListenToTable(name string, ch chan Data, quit map[*websocket.Conn](chan bool), ws *websocket.Conn) {
go func(name string, ws *websocket.Conn) {
for {
select {
case <-quit[ws]:
fmt.Println("QUIT GOROUTINE")
break
default:
res, err := r.Table(name).Changes().Run(db.session)
if err != nil {
log.Fatalln(err)
}
var response DataFeed
for res.Next(&response) {
response.NewVal.WS = ws
ch <- response.NewVal
}
if res.Err() != nil {
log.Println(res.Err())
}
}
}
}(name, ws)
}
I have also tried buffered channels or pass the channel instead of the map of channels to the goroutine but without success. The fmt.Println("QUIT GOROUTINE")
command is never called and the goroutine isn't sopped.
I hope someone can help me and sorry if this question was already ask but I haven't found a solution that solves my problem.
答案1
得分: 1
首先让事情变得简单一些:
据我所见,你不需要一个全局注册表来存储退出通道。只需在主函数中创建一个 ch := make(chan bool)
,将其传递给 ListenToTable
(而不是整个通道映射),并在 select 语句中使用它。如果要退出,可以在主函数中使用 close(ch)
。但正如你所说,这并不能解决你的问题。
从理论上讲,你在关闭 Go 协程方面是正确的。我使用你的示例代码,并从中创建了以下可运行的代码:
package main
import (
"fmt"
"time"
)
func main() {
chClose := make(chan bool)
channel := make(chan string)
ListenToTable("somestring", channel, chClose)
time.Sleep(3 * time.Second)
chClose <- true
time.Sleep(1 * time.Second)
}
func ListenToTable(name string, ch chan string, chClose chan bool) {
go func(name string) {
for {
select {
case <-chClose:
fmt.Println("QUIT GOROUTINE")
return // 非常重要:不要使用 break!
default:
}
}
}(name)
}
问题肯定出在你的代码中的其他地方,可能是在 default
部分被某些东西阻塞,甚至没有执行 select 语句。尝试在 select {
之前打印 fmt.Println("something")
。如果无法正常打印,那么你就找到了答案。
还有一件事:如上面的代码中所注释的,你不能使用单个 break
从 for { select { ... } }
循环中跳出。你需要使用 return
(退出函数)或其他策略(如 Adrian 在评论中建议的带标签的 break)。break
只会退出 select,而不会退出 for 循环。
英文:
First to make things easier:
As far as I can see you do not need a global register for the quit channels. Just create a ch := make(chan bool)
in main, pass it to ListenToTable
(instead of the whole map of channels) and use it in the select. In main close(ch)
it if you want to exit. But as you said, that doesn't solve your problem.
Theoretically you are on the right track with closing the go routine. I took your sample code and made the following runnable code from it:
package main
import (
"fmt"
"time"
)
func main() {
chClose := make(chan bool)
channel := make(chan string)
ListenToTable("somestring", channel, chClose)
time.Sleep(3 * time.Second)
chClose <- true
time.Sleep(1 * time.Second)
}
func ListenToTable(name string, ch chan string, chClose chan bool) {
go func(name string) {
for {
select {
case <-chClose:
fmt.Println("QUIT GOROUTINE")
return // VERY IMPORTANT: not break!
default:
}
}
}(name)
}
The problem must be with something else in you code, probably blocked by something in the default
section and not even executing the select. Try printing fmt.Println("something")
before the select {
. If that is not printed regularly then you have your answer.
One more thing: As commented in the code above you cannot break out of a for { select { ... } }
with a single break
. You need to use a return
(to exit the function) or another tactic (like a break with a label as Adrian suggested in the comments). The break
will only exit the select
, but not the for
loop.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论