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

时间:2017-01-13 07:13:36

标签: go 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)
}

另外,如果我将默认子句保留为未注释,则可以连续打印“now”。 任何人都可以解释这会发生什么?

1 个答案:

答案 0 :(得分:0)

问题是goroutine是异步运行的。 使用a.modify()时,您的代码的行为与此类似:

  1. a.ticker.C
  2. 打勾
  3. 停止旧的自动收报机,并创建新的a.ticker.C
  4. 使用a.ticker.C
  5. 等待select

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

    如果你在goroutine中做2.它可以按照以下顺序完成

    1. a.ticker.C
    2. 打勾
    3. 使用a.ticker.C
    4. 等待select
    5. 停止旧的自动收报机,并创建新的a.ticker.C
    6. 在这种情况下,在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毫秒。
      7. .....

        1. 等待a.ticker.C,打勾,致电go a.modify()
        2. 关键是即使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.