心澄

命由己造,相由心生,世间万物皆是化相,心不动,万物皆不动,心不变,万物皆不变
Program Languagerusting... going...
Email insunsgmail.com
Country China
LocationHangZhou, ZheJiang

记一个golang channel select的坑


先上代码:

package main

import (
    "fmt"
    "time"
)

func main() {
    stop := make(chan struct{})
    i := 0
    go channel(i, stop)
    wait := make(chan struct{})
    wait <- struct{}{}
}

func channel(i int, stop chan struct{}) {
    select {
    case stop <- struct{}{}:
    default:
        break
    }
    for {
        select {
        case <-stop:
            fmt.Println("stop:", i)
            return
        default:
            fmt.Println("channel:", i)
            go channel(i+1, stop)
            //下面的低吗将导致stop信号几乎无法接收到,进而导致协程无法退出
            time.Sleep(time.Second)
        }
    }
}

这段代码可以在go playground上测试,点击这里

上面的代码本来希望在调用channel()这个方法的时候,停止掉其他的协程(如果已经有开启的话)。运用场景如:客户端与服务器端通过websocket进行交互,当用户订阅某一类消息时(比如股票行情数据),在新的订阅提交后须取消之前其他的订阅,否则将导致已不需要的订阅数据继续在发送。
但是实际上,上面这段代码的运行结果并不能达到预期效果,输出类似如下:

channel: 0
channel: 1
channel: 2
channel: 3
channel: 4
...

可以发现几乎没有任何协程被停止,越到后面恐怖,严重的情况会把服务器的资源耗尽。
最主要的问题在于default分支的time.Sleep(time.Second),这里的Sleep将导致发送停止信号的case ch <- struct{}{}:这里执行变得几乎无法成功。

对代码改造如下:

package main

import (
    "fmt"
    "time"
)

func main() {
    stop := make(chan struct{})
    i := 0
    go channel(i, stop)
    wait := make(chan struct{})
    wait <- struct{}{}
}

func channel(i int, stop chan struct{}) {
    select {
    case stop <- struct{}{}:
    default:
        break
    }
    timer := time.NewTimer(time.Millisecond)
    ticker := time.NewTicker(time.Second)
    for {
        select {
        case <-stop:
            fmt.Println("stop:", i)
            return
        case <-ticker.C:
            fmt.Println("channel:", i)
            // 这里用来模拟用户二次订阅
            go channel(i+1, stop)
        // 通过下面的分支可以立即执行而不用等ticker
        case <-timer.C:
            timer.Stop()
            fmt.Println("channel:", i)
        }
    }
}

上面的代码能如预期地执行,输入结果类似如下:

channel: 0
channel: 0
stop: 0
channel: 1
channel: 1
...

想在 go playground上执行,点击这里

  • 分享:
评论

    • 博主

    说点什么