Golang非阻塞通道不起作用

时间:2017-07-04 18:08:55

标签: go websocket goroutine

我第一次使用goroutines和频道工作,不再进一步。

我有一个websocket连接,每次用户连接一个新的goroutine时都会产生。现在,如果用户断开与websocket连接的连接,我想停止这个goroutine。

为了管理停止信号,我创建了一个频道地图。每个条目都可以由用户websocket连接标识。我将websocket连接,停止信号的通道映射和另外两个参数传递给goroutine。但是goroutine没有从戒烟渠道收到任何价值,我不知道为什么。

以下是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
        }
    }
}

创建goroutine的data包的代码:

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。

我希望有人可以帮助我,如果这个问题已经提出,我很抱歉,但我找不到解决方案来解决我的问题。

1 个答案:

答案 0 :(得分:1)

首先让事情变得简单:

据我所知,您不需要全局注册戒烟渠道。只需在main中创建一个ch := make(chan bool),将其传递给ListenToTable(而不是整个频道地图)并在选择中使用它。在主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  // VERY IMPORTANT: not break!
            default:
            }
        }
    }(name)
}

问题必须与代码中的其他内容有关,可能会被default部分中的内容阻止,甚至不执行select。尝试在fmt.Println("something")之前打印select {。如果没有定期打印,那么你有答案。

还有一件事:正如上面代码中所评论的那样,您只能使用单个for { select { ... } }来突破break。您需要使用return(退出该功能)或其他策略(如评论中Adrian建议的标签中断)。 break只会退出select,但不会退出for循环。