【问题标题】:How to make this golang for-select code work?如何使这个 golang for-select 代码工作?
【发布时间】:2021-07-21 11:34:26
【问题描述】:

问题

如何让下面的代码在 3 秒后打印“QUIT”?

代码

package main

import (
    "fmt"
    "time"
)

func main() {
    quit := make(chan struct{})
    tasks := make(chan struct{})

    go func(){
      time.Sleep(1 * time.Second)
      tasks <- struct{}{}
    }()

    go func(){
      time.Sleep(3 * time.Second)
      quit <- struct{}{}
    }()

    for {
        select {
        case <-quit:
            fmt.Println("QUIT")
            return
        case <-tasks:
            fmt.Println("Doing")
            // do some long time jobs more than 10 seconds
            time.Sleep(10 * time.Second)
        }
    }
}

观察

上面的代码打印出“Doing”。并休眠 10 秒,然后打印“QUIT”。 如何中断这个睡眠,让它在3秒后接收quit频道并打印“QUIT”?

好像selectcase tasks屏蔽了,3秒后就收不到quit频道了。

【问题讨论】:

  • 删除return
  • select 似乎被 case 任务阻塞了 不,程序刚结束,你回来了。
  • 如何中断这个睡眠 - 你想在这里解决什么问题? “杀死”一个阻塞操作?
  • 如何让它在3秒后退出?我已经更新了这个问题。
  • 假设case tasks内部有一个耗时的工作,超过10秒,那么当我发送quit频道信号时如何中断这个工作?

标签: go select goroutine


【解决方案1】:

要发出异步任务结束的信号,最好关闭通道,这对于防止许多导致各种死锁的误用非常重要。 在您的原始代码中,我会写 close(quite) 而不是 quit &lt;- struct{}{} 请记住,在关闭时读取不会阻塞并始终返回零值,这就是诀窍。

无论如何,除此之外,解决问题的一种优雅方法是结合使用context.Contexttime.After

time.After 将帮助您阻止可选择的任务集。

context.Context 更适合处理此类信号。

https://play.golang.org/p/ZVsZw3P-YHd

package main

import (
    "context"
    "log"
    "time"
)

func main() {
    log.Println("start")
    defer log.Println("end")
    ctx, cancel := context.WithCancel(context.Background())
    tasks := make(chan struct{})

    go func() {
        time.Sleep(1 * time.Second) // a job
        tasks <- struct{}{}
    }()

    go func() {
        time.Sleep(3 * time.Second) // a job
        cancel()
    }()

    for {
        select {
        case <-ctx.Done():
            log.Println("QUIT")
            return
        case <-tasks:
            log.Println("Doing")
            // do some long time jobs more than 10 seconds
            select {
            case <-ctx.Done():
                return
            case <-time.After(time.Second * 10):
            }
        }
    }
}

【讨论】:

    【解决方案2】:

    第二个作业运行了 10 秒,所以它会在这段时间内阻塞循环,并且在这个作业完成之前不会收到您发送到quit 频道的信号。

    为了打断这个耗时的工作,也许你可以把它拆分成另一个 goroutine。这样的事情会做:

    go func() {
      for {
        select {
        case <-tasks:
          fmt.Println("Doing")
          // do some long time jobs more than 10 seconds
          time.Sleep(10 * time.Second)
        }   
      }   
    }() 
    
    <-quit
    fmt.Println("QUIT")
    

    【讨论】:

      【解决方案3】:

      使用 [sync.WaitGroup.

      会更容易
      package main
      
      import (
          "fmt"
          "sync"
          "time"
      )
      
      func worker(msg string, duration time.Duration, doing bool, wg *sync.WaitGroup) {
          defer wg.Done()
          time.Sleep(duration)
      
          fmt.Println(msg)
      
          if doing {
              time.Sleep(10 * time.Second)
          }
      }
      
      func main() {
      
          var wg sync.WaitGroup
          msgs := [2]string{"QUIT", "Doing"}
          durations := [2]time.Duration{3 * time.Second, 1 * time.Second}
          doing := [2]bool{false, true}
      
          for i, msg := range msgs {
              wg.Add(1)
      
              go worker(msg, durations[i], doing[i], &wg)
          }
      
          wg.Wait()
      }
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-23
        • 2010-12-13
        • 2013-04-23
        • 2015-04-09
        • 2013-07-24
        相关资源
        最近更新 更多