为了账号安全,请及时绑定邮箱和手机立即绑定

golang中的Sleep会阻塞其他goroutine吗?

golang中的Sleep会阻塞其他goroutine吗?

Go
杨__羊羊 2023-08-07 14:31:46
实际上我正在尝试做这样的事情:我有一个生产者和一个消费者,消费者每隔几分钟检查一次并计算通道中的事件。但是当我尝试在 go Playground 中测试它时,我发现:package mainimport (    "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,输出都会是:default0default2default4default6default8default10defaultstop10over为什么goroutine只能将2个事件放入通道中?我想要这样的东西:default0defaultstop10over问题是通道大小,我只是从其他代码复制而不检查......
查看完整描述

3 回答

?
汪汪一只猫

TA贡献1898条经验 获得超8个赞

这个功能:


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。匿名发件人仍在喘口气,或者类似的事情,并且还没有将第三个值输入到通道中,因此您的循环检测到通道为空并采用该子句default,这会在此处中断您的循环。


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


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


程序运行的其余部分会重复此操作。


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


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


2或者,在本例中,只要主 Goroutine 给它再次运行的机会即可。这具体取决于 Playground VM 的单 CPU 方面。


查看完整回答
反对 回复 2023-08-07
?
SMILET

TA贡献1796条经验 获得超4个赞

[D]oes sleep in golang 会阻塞其他 goroutine 吗?

不。


查看完整回答
反对 回复 2023-08-07
?
慕无忌1623718

TA贡献1744条经验 获得超4个赞

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

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


查看完整回答
反对 回复 2023-08-07
  • 3 回答
  • 0 关注
  • 199 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信