在golang中睡眠会阻止其他goroutine吗?

时间:2019-11-22 07:46:20

标签: go goroutine

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

我有一个生产者和一个消费者,消费者每隔几分钟检查一次,并统计频道中的事件。但是,当我尝试在操场上进行测试时,我发现:

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")

}

无论我睡多长时间,第二次/ 10次。第二次* 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

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

3 个答案:

答案 0 :(得分:3)

此功能:

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 您的循环回到顶部,并检查{{1}中是否有项目}。存在,因此您的循环将对其进行计数:现在您又获得了两项。您的循环返回顶部,并检查c1中是否还有其他项目。匿名发件人仍在喘口气,或者类似的话,并且没有在通道中得到 third 值-因此您的循环会检测到该通道为空,并使用c1子句,这会中断您的循环。

正在运行default的goroutine现在打印行main,检查是否应该停止(否),并暂停一秒钟。在所有这些过程中的某个时间(实际上是在暂停时),匿名发件人有机会运行,将一个项目放到通道中,然后在试图将第二个项目放到通道中时阻塞。这只需要几十或数百纳秒。

default呼叫和您的匿名发送者一样仍被阻止,但是唤醒将在几分之一秒内发生。完成后,您的主循环返回顶部,以运行内部Sleep循环,该循环从!bk中读取项目。您现在的状态与上次相同,因此这次您还将从c1中读取两项。

在程序的其余部分重复此操作。

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


1 实际上,第一次运行时,主例程正在运行,匿名发送者尚未启动,因此计数为零。主goroutine在其c1调用中被阻止,该调用使匿名发件人可以在单个CPU上运行,从而使您可以通过主例程进行 second 行程。

2 或者,在这种情况下,只要主goroutine有机会再次运行它,就可以。具体取决于游乐场虚拟机的单CPU方面。

答案 1 :(得分:1)

  

[D] oes在golang中睡眠会阻止其他goroutine吗?

否。

答案 2 :(得分:0)

    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")

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