在 select{case:channel} 中更改频道

Change a channel in select{case:channel}

我使用 Ticker 定期执行任务,但在更改它时遇到了一些问题。我会在收到一些消息时将自动收报机更改为新的自动收报机并更改间隔。这是将重现此问题的示例代码:

package main

import (
    "fmt"
    "time"
)

type A struct {
    ticker *time.Ticker
}

func (a *A) modify() {
    a.ticker.Stop()
    a.ticker = time.NewTicker(time.Second)
}
func main() {
    a := new(A)
    a.ticker = time.NewTicker(time.Second)
    go func() {
        for {
            select {
            case <-a.ticker.C:
                fmt.Println("now")
                go a.modify()
                /*
                    default:
                        //fmt.Println("default")
                        time.Sleep(time.Millisecond * 100)
                */
            }
        }
    }()
    time.Sleep(time.Second * 60)
}

"now" 只会打印一次。但是,如果我删除 "go",它将连续打印,如下所示:

package main

import (
    "fmt"
    "time"
)

type A struct {
    ticker *time.Ticker
}

func (a *A) modify() {
    a.ticker.Stop()
    a.ticker = time.NewTicker(time.Second)
}
func main() {
    a := new(A)
    a.ticker = time.NewTicker(time.Second)
    go func() {
        for {
            select {
            case <-a.ticker.C:
                fmt.Println("now")
                a.modify()
                /*
                    default:
                        //fmt.Println("default")
                        time.Sleep(time.Millisecond * 100)
                */
            }
        }
    }()
    time.Sleep(time.Second * 60)
}

此外,如果我不对 default 子句进行注释,则可以连续打印 "now"。 谁能解释一下这是怎么发生的?

问题是goroutine是异步运行的。 使用 a.modify():

时你的代码表现得像这样
  1. a.ticker.C
  2. 获取报价
  3. 停止旧代码,创建新代码a.ticker.C
  4. 等待 a.ticker.C 使用 select

在这种情况下,在 2. 中新创建的 a.ticker.C 与 3. 中等待的通道相同

如果你在 goroutine 中执行 2.可以按以下顺序完成

  1. a.ticker.C
  2. 获取报价
  3. 等待 a.ticker.C 使用 select
  4. 停止旧代码,创建新代码a.ticker.C

在这种情况下,2. 中等待的频道与 3. 中新创建的频道不同。 由于选择频道是已停止的旧频道,因此它永远不会得到任何滴答声。

您可以确认此行为插入一些 fmt.Printf 并观察 a.ticker.C 的地址。

func (a *A) modify() {
    a.ticker.Stop()
    fmt.Printf("ticker stopped: %p\n", &a.ticker.C)
    a.ticker = time.NewTicker(time.Second)
    fmt.Printf("new ticker created: %p\n", &a.ticker.C)
}
func main() {
    a := new(A)
    a.ticker = time.NewTicker(time.Second)
    go func() {
        for {
            fmt.Printf("waiting for ticker: %p\n", &a.ticker.C)
            select {
        ....

a.modify():

waiting for ticker: 0xc420010100
ticker stopped: 0xc420010100
new ticker created: 0xc420068000
waiting for ticker: 0xc420068000
ticker stopped: 0xc420068000
new ticker created: 0xc420068080
waiting for ticker: 0xc420068080

go a.modify():

waiting for ticker: 0xc420010100
waiting for ticker: 0xc420010100
ticker stopped: 0xc420010100
new ticker created: 0xc420066040

您可以看到 go a.modify() 您不是在等待新创建的频道。

更新默认行为:

default:go a.modify() 一起使用时,它会像这样。

  1. 等待 a.ticker.C,打勾,调用 go a.modify() 执行 3.
  2. 等待 a.ticker.C,什么也没得到,所以回退到默认值并休眠 100 毫秒。
  3. 停止旧代码,更新a.ticker.C
  4. 等待 a.ticker.C,什么也没得到,所以回退到默认值并休眠 100 毫秒。
  5. 等待 a.ticker.C,什么也没得到,所以回退到默认值并休眠 100 毫秒。
  6. 等待 a.ticker.C,什么也没得到,所以回退到默认值并休眠 100 毫秒。

.....

  1. 等待 a.ticker.C,得到报价,致电 go a.modify()

重点是 for{} 循环可以继续,即使你从 a.ticker.C 那里什么也得不到。 您可以使用相同的代码确认行为。

waiting ticker: 0xc420010100     <-- 1.
now                              <-- 1.
waiting ticker: 0xc420010100     <-- 2.
default                          <-- 2.
ticker stopped: 0xc420010100     <-- 3.
new ticker created: 0xc420066240 <-- 3.
waiting ticker: 0xc420066240     <-- 4.
default                          <-- 4.