【问题标题】:Why I need to sleep twice to see goroutines?为什么我需要睡两次才能看到 goroutines?
【发布时间】:2023-04-09 12:02:01
【问题描述】:

我用 go 编写了这个小程序。关于go关键字,我只知道当我以这种方式调用一个函数时,是同时执行的。我尝试在没有 time.Sleep() 的情况下执行此代码,但没有生成输出。我需要添加 time.Sleep(1000) 两次。一次。睡眠声明是不够的。为什么?

package main

import (
    "fmt"
    "time"
)

func doSomething(integer int) {
    fmt.Println(integer)
}

func main() {
    i := 1

    for i <= 10 {
        go doSomething(i)
        i++
    }

    time.Sleep(1000)
    time.Sleep(1000)
}

【问题讨论】:

  • 这是a very good answer,与您的问题基本相同。
  • @JohnCarpenter 只需在 go 操场上运行他的示例。一旦 goroutines 被启动,执行就完成了,因为没有进一步的语句。你如何运行它实际上并不重要。如果它是一个 exe,并且您双击它,则窗口将关闭,如果您打开命令提示符并调用该 exe,则窗口将保持打开状态并移动到下一个提示符。
  • 因为调度 goroutine 和调用 fmt.Println(integer) 需要超过 1 微秒。
  • @sensorario: 是的,但由您自己协调,无论是使用频道还是同步。WaitGroup
  • 请先看书(this one 将是一个好的开始,请参阅this 获取列表)。尝试一些东西很好,但是在没有任何基础知识的情况下这样做主要是在浪费时间——无论是你的还是我们的。

标签: go concurrency


【解决方案1】:

这是因为当您使用go(即作为goroutine)调用函数时,它会在后台运行并继续执行。在这种情况下,在调用 goroutine 后,程序没有任何事情可做,所以它只是完成并且您看不到任何输出。为什么需要两个?好吧,您没有,只是 1 秒的睡眠时间不够长,如果您有 time.Sleep(2000),您可能会得到相同的结果(尽管不能保证)。通常,在使用 goroutine 时,您需要在某些地方使用某种阻塞语句。 imo,最简单的方法是向 goroutine 传递一个通道,然后从中接收。

由于您的代码结构,这不是最有用的示例,但这是您的程序的修改版本,它将阻塞直到第一个 goroutine 完成。由于您实际上调用了 10 个 goroutine,因此您需要更强大的逻辑来阻止它们,直到它们完成,但更合理的解决方案是将循环放入 gouroutine,而不是调用 10 个 goroutine。 https://play.golang.org/p/66g9u8Bhjj

另外,请在 go tour 中查看这个更真实的示例; https://tour.golang.org/concurrency/2

【讨论】:

  • time.Sleep(10000) 不够。但程序不会等待 10 秒。
  • @sensorario 没有时间可以保证工作,这就是为什么你应该使用渠道。 goroutine 应该告诉调用者它已经完成而不是依赖巧合。
  • @sensorario: time.Sleep(10000) 是 10 微秒。使用时间包中定义的常量,time.Sleep(10*time.Second)
  • @JohnCarpenter:这个问题不难回答:golang.org“Go 是一种开源编程语言……”
  • @JohnCarpenter,考虑阅读this document——它解释了 Go 运行时如何在 OS 线程上实现 goroutines 的 M×N 调度。
【解决方案2】:

试试这个:

package main

import (
    "fmt"
    "runtime"
    "time"
)

func doSomething(integer int) {
    fmt.Println(integer)
}

func main() {
    i := 1

    for i <= 10 {
        go doSomething(i)
        i++
    }
    runtime.Gosched()
    time.Sleep(10 * time.Millisecond)
}

它会以不同的方式运行。

你知道完成这个(你的)代码需要多少时间吗:
(考虑 CPU Loaded 100% with other processes with higher priority.)

func doSomething(integer int) {
    fmt.Println(integer)
}
for i <= 10 {
    go doSomething(i)
    i++
}

简单的答案:没有。
这取决于每个系统上的许多未知因素:
1- CPU 速度和 CPU 内核数和 CPU 缓存...
2- RAM 速度和总线速度和总线矩阵(是否并发?)
2- 操作系统调度程序和操作系统负载和优先级
3- Golang 调度器
4- CPU 负载
...

我的意思是: 在并发系统和并发编程中,依赖简单的代码(或汇编)指令时序并不是一个好主意。

是的,很高兴了解 Golang 调度程序及其工作原理,但是 即使你知道 Golang 调度器的行为,你知道 OS 现在在做什么吗?

仅在实时操作系统中,它估计为毫秒左右范围内的某个已知时间。

有时最好限制并发:
这是限制并发的一种方法:

//using chan to limit the concurrency
package main

import (
    "os/exec"
    "runtime"
    "sync"
)

func main() {
    numberOfJobs := 10
    c := make(chan *exec.Cmd, numberOfJobs)
    for i := 0; i < numberOfJobs; i++ {
        c <- exec.Command("notepad")
    }
    close(c)

    nCPU := runtime.NumCPU()
    var wg sync.WaitGroup
    for i := 0; i < nCPU; i++ {
        wg.Add(1)
        go func() {
            for cmd := range c {
                cmd.Run()
            }
            wg.Done()
        }()
    }

    wg.Wait()
}

这是限制并发的另一种方式:

//using chan to limit the concurrency
package main

import (
    "os/exec"
    "runtime"
    "sync"
)

func main() {
    n := runtime.NumCPU()
    dummy := make(chan bool, n)
    numberOfJobs := 10
    var wg sync.WaitGroup
    for i := 0; i < numberOfJobs; i++ {
        cmd := exec.Command("notepad")
        dummy <- true // wait here if no CPU available
        wg.Add(1)
        go func(cmd *exec.Cmd) {
            //defer func() { <-dummy }()
            cmd.Run()
            wg.Done()
            <-dummy
        }(cmd)
    }
    close(dummy)

    wg.Wait()
}

我希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-30
    相关资源
    最近更新 更多