【问题标题】:does Sleep in golang block other goroutine?在 golang 中睡眠会阻塞其他 goroutine 吗?
【发布时间】:2020-03-18 06:10:47
【问题描述】:

其实我正在尝试做这样的事情:

我有一个生产者和一个消费者,消费者每隔几分钟检查一次并计算频道中的事件。但是当我尝试在 go playground 中测试它时,我发现:

package main

import (
    "fmt"
    "time"
)

func main() {

    c1 := make(chan string, 1)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {

            c1 <- "result 1"
        }
        quit <- 1
    }()

    count := 0
    for stop := false; !stop; {
        for bk := false; !bk; {
            select {
            case _, ok := <-c1:
                if ok {
                    count++
                }
            default:
                bk = true
                fmt.Println("default")
            }
        }
        select {
        case <-quit:
            fmt.Println("stop")
            stop = true
        default:
        }
        fmt.Println(count)
        time.Sleep(time.Second / 10)

    }
    fmt.Println("over")

}

不管我睡多久,从time.Second/10到time.Second*10, 输出将是:

default
0
default
2
default
4
default
6
default
8
default
10
default
stop
10
over

为什么 goroutine 只能在通道中放置 2 个事件? 我想要类似的东西:

default
0
default
stop
10
over

问题是通道大小,我只是从其他代码中复制而不检查...

【问题讨论】:

  • 我承认我不知道你的代码试图做什么。
  • 正如我所提到的,消费者每隔几秒就会尝试对频道中的事件进行计数。到目前为止,我希望消费者检索频道中的所有事件。但是输出显示 goroutine 以与消费者检查它相同的速度推送值。无论消费者在每次检查后休眠多长时间,都会收到 2 个新事件。
  • 如果您想在频道中添加更多事件,为什么要将其大小设置为只能容纳 1 个?
  • 你是对的,问题是频道大小。

标签: go goroutine


【解决方案1】:

这个函数:

go func() {
    for i := 0; i < 10; i++ {

        c1 <- "result 1"
    }
    quit <- 1
}()

总是——好吧,总是在它可以运行的时候——试图将一个字符串放入频道c1(直到它放入十个,无论如何)。

制作了频道c1与:

c1 := make(chan string, 1)

所以它有空间容纳一个待处理的项目。因此,如果通道为空,则循环放入一个项目,然后尝试放入第二个项目。如果此时通道已满——不能保证它是满的,但暂时假设它是满的——这个 goroutine 现在暂停,等待有人将前一个项目从通道中拉出。

同时,在正负几纳秒的时间里——或者可能在其他 goroutine 块之前或之后1——你正在运行这段代码。 (没有保证是这种情况,但实际上这种情况。)

    for bk := false; !bk; {
        select {
        case _, ok := <-c1:
            if ok {
                count++
            }
        default:
            bk = true
            fmt.Println("default")
        }
    }

此代码检查频道中是否有任何内容。因为匿名发件人将一件物品放入其中,所以频道确实 里面有东西。此代码删除一个项目,在通道中创建空间。这导致匿名发件人中的阻止发送现在运行一步。没有保证它会这样做,但事实上,它确实会这样做 - 所以现在频道中有另一个项目。

不过,运行这一步后,匿名发件人现在会暂停几纳秒。2您的循环返回顶部并检查c1 中是否有项目。有,所以你的循环接受它并计算它:你现在已经接受了另外两个项目。您的循环返回顶部并检查c1 中是否还有其他项目。匿名发件人仍在喘气,或者类似的事情,并且没有在通道中获得 third 值 - 因此您的循环检测到通道是空的并采用default 子句,这打破了你的循环。

运行main 的goroutine 现在打印default 行,检查它是否应该停止(否),并暂停几分之一秒。在所有这些过程中的某个时间——实际上,在暂停时——匿名发送者有机会运行,将一个项目放入通道,并在它试图将第二个项目放入通道的点处阻塞。这只需要几十或几百纳秒。

Sleep 呼叫仍被阻止,您的匿名发件人也是如此,但唤醒将在几分之一秒内发生。当它发生时,你的主循环回到顶部,运行内部!bk 循环,从c1 中读取项目。您现在处于与上次相同的状态,因此您这次也将读取 c1 中的两个项目。

这会在程序运行的其余部分重复。

这里的几个步骤不保证以这种方式发生。考虑到当前的实现以及您在单 CPU 虚拟机上运行所有这些的事实,它们只是碰巧实际上以这种方式运行。如果这两种情况中的任何一种发生变化(例如,如果您在多 CPU 系统上运行,或者实现被修改),您的程序的行为可能会发生变化。


1实际上,第一次通过时,主程序正在运行,匿名发件人尚未启动,因此计数为零。主 goroutine 阻塞在其 Sleep 调用中,这允许匿名发送者在单个 CPU 上运行,为您准备第二次通过主例程。

2或者,在这种情况下,只要主 goroutine 给它一个再次运行的机会。这具体取决于 Playground VM 的单 CPU 方面。

【讨论】:

    【解决方案2】:

    [D]oes 在 golang 中休眠会阻塞其他 goroutine?

    没有。

    【讨论】:

      【解决方案3】:
          for stop := false; !stop; {
      
              for bk := false; !bk; {
                  select {
                  case _, ok := <-c1:
                      // --------- add this ---------
                      time.Sleep(time.Second / 100)
                      if ok {
                          count++
                      }
                  default:
                      bk = true
                      fmt.Println("default")
                  }
              }
      
              select {
              case <-quit:
                  fmt.Println("stop")
                  stop = true
              default:
              }
              fmt.Println(count)
              time.Sleep(time.Second / 10)
      
          }
          fmt.Println("over")
      

      因为频道比“选择默认”慢。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-19
        • 1970-01-01
        • 1970-01-01
        • 2011-07-23
        相关资源
        最近更新 更多