【问题标题】:Will time.Sleep block goroutine?time.Sleep 会阻塞 goroutine 吗?
【发布时间】:2020-06-23 05:08:20
【问题描述】:

我有下一个代码:

par.go

package main

import (
    "runtime";
    "time"
)

func main() {
    runtime.GOMAXPROCS(4)
    ch := make(chan int)
    n := 1
    for i := 0; i < n; i++ {
        go func() {
            time.Sleep(60 * time.Second)
            ch <- 1
        }();
    }
    for i := 0; i < n; i++ {
        <-ch
    }
}

我使用 next 来运行它:

$ go build par.go
$ time ./par

然后,确认这个进程有多少线程:

$ ps -ef | grep par
shubunt+  3670 32131  0 12:35 pts/0    00:00:00 ./par
$ cat /proc/3670/status | grep -i threads
Threads:        5

你可以看到有 5 个线程。

如果我在代码中更改n 的值,那么情况如下:

n := 100, Threads is 8
n := 10000, Threads is 9
n := 100000, Threads is 9
n := 1000000, Threads is 9
n := 2000000, Threads is 10

我知道,去调度器遵循MPG 模型,这里是P = 4,所以M = 4,M 是1:1 和KSE(内核线程)。如果任何 goroutine 处于任何阻塞状态,P 将与当前的M 分离,并找到一个空闲的M,如果找不到则新的M。

所以,我的问题是:time.Sleep 真的会阻塞 goroutine 吗?如果没有,当我将n 的值从1 增加到2000000 时,为什么会有新线程?如果是,那里有60 seconds,为什么只是调度程序新一点点M,我希望那里有很多新线程?

更新:

添加来自this 的另一个示例。

test.go:

package main

import (
    "io/ioutil"
    "os"
    "runtime"
    "strconv"
)

func main() {
    runtime.GOMAXPROCS(2)
    data := make([]byte, 128*1024*1024)
    for i := 0; i < 200; i++ {
        go func(n int) {
            for {
                err := ioutil.WriteFile("testxxx"+strconv.Itoa(n), []byte(data), os.ModePerm)
                if err != nil {
                    println(err)
                    break
                }
            }
        }(i)
    }
    select {}
}

如果不使用Sleep,则使用真正的IO,我机器上的线程数将是202。

那么,我的问题也与上面两个例子的区别有关,什么时候我应该担心调度程序为我生成太多的内核线程?

【问题讨论】:

    标签: go


    【解决方案1】:

    [I]s time.Sleep 真的阻塞了 goroutine?

    是的。

    但是如何将 goroutine 实际调度到线程是 a) 复杂的,b) 每个版本都不同,c) 可能因架构而异,并且 d) 不是由语言指定的。虽然“MPG 模型”是一个准确的模型,但如果调度程序是如何工作的,它只是一个模型。

    如果调度程序确定 10 个线程足以不运行 200'000 个 goroutine,因为它们都是 time.Sleeping,那么 10 个线程就足够了。

    在 Go 中基本上没有什么可担心或考虑的事情(与其他必须特别注意这些特性的语言形成鲜明对比)。 “阻塞”只是意味着下一条语句不能立即执行,因为实际的语句还没有完成。如果时间不等,这可能会发生很多情况。睡眠除了等待、等待 RAM、等待磁盘或等待网络数据之外什么都不做。以相同的方式处理所有内容会简化调度程序,但会使调度程序变得很糟糕。所以不,time.Sleep 不会阻塞 goroutine。问题是“block goroutine”没有明确的含义。而且它不需要定义,因为它没有什么有趣的知识。

    更新:

    [W]我什么时候应该担心调度程序会为我生成过多的内核线程?

    从不。

    有两种不同的场景:A) 编写正常、合理的生产代码和 B) 编写精心设计的手工代码,以创建大量线程,这些线程都在磁盘 IO 中等待完成。当然,您可以故意欺骗调度程序和操作系统,并提出一个创建过多线程的病态程序(调度程序 可以 被欺骗的问题在 issue #4056 中得到解决),但这不是需要担心的事情。只是不要故意做愚蠢的事情。

    有很多方法可以欺骗您的计算机。编写活泼的代码是一种方法。种族检测器有助于识别它们。在编写它们之前担心竞争条件是一件好事(发生这种情况)。创建太多线程可能会发生,您可以向操作系统询问线程数。万一有太多:修复它。但这不太可能。它有点像 OOM 错误:编写 OOM 的代码非常简单,但在编写代码时无需经常担心 OOM。如果您遇到OOM,您重新设计,但您不会通过担心OOM以及您需要了解OOM,如何防止它以及如何处理它来开始任何琐碎的项目。除非您已经知道您的数据使用 很多 内存。同样在这里:如果您知道您的代码将执行 ma​​ssive concurrent 磁盘 IO 并且这是域的固有,那么您可能会担心这一点并在代码中处理此问题,但每种语言的技术都是相同的。

    【讨论】:

    • 我不太明白。其实这里有个话题:stackoverflow.com/questions/28186361/…,在里面,不用sleep,直接用io。我试过了,它确实生成了 200+ 个线程,我认为这可能是内存浪费。所以不知道你说的there is nothing interesting to know about it是什么意思?我只是想知道,在什么情况下我应该注意避免goroutine阻塞,然后不要让调度器为我生成太多线程。
    • 其实我也不太明白,为什么要在goroutine里说block。我的意思是如果使用epoll,所有IO都是非阻塞的?不是同一个概念?
    • 如果我们不用担心,为什么要在那里github.com/golang/go/issues/4056?
    • @atline 你把很多东西混为一谈了。另一个问题与您的问题相同:将未指定的内容过度简化。您链接的问题是 开发 调度程序的人所担心的问题,他们确实清楚地了解操作系统和硬件级别上发生的事情,但这不是 Go 开发人员必须要做的事情担心。你不会从这些实验中学到任何东西,也不会写出更好的程序(为了清楚起见有点夸张)。
    • "如果使用epoll,所有IO都是非阻塞的吗?"嗯,不。 epoll 是 Linux 上的一种机制,可让您查看文件描述符,并且还有其他(较旧的)此类机制,例如 select。 epoll 不是你喊“epollius!”的魔法咒语。无论这意味着什么,所有 IO 都神奇地变成了“非阻塞”。 IO 仍然是一个阻塞操作,使用 epoll 所能做的就是以一种非常有效的方式通知几个 IO 操作中的哪一个已准备好向前移动,以便您可以再次尝试它们。
    【解决方案2】:

    总结一下我对这个问题的最终理解,以防有人感兴趣:

    1. 对于time.Sleep,如果我设置启动20个goroutine,接下来是delve的结果:

       ./dlv attach 2070
       Type 'help' for list of commands.
       (dlv) threads
       * Thread 2070 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
         Thread 2071 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
         Thread 2072 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
         Thread 2092 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
         Thread 2093 at 0x44d003 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
       (dlv) grs
         Goroutine 1 - User: /home/shubuntu1/gotrial/new/par.go:19 main.main (0x452686)
         Goroutine 2 - User: /usr/lib/go-1.12/src/runtime/proc.go:302    runtime.gopark (0x425f3f)
         Goroutine 3 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x425f3f)
         Goroutine 4 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 5 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 6 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 7 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 8 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 9 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 10 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 11 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 12 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 13 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 14 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 15 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 16 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 17 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 18 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 19 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 20 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 21 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 22 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
         Goroutine 23 - User: /usr/lib/go-1.12/src/runtime/proc.go:307 time.Sleep (0x43cc69)
       * Goroutine 24 - User: /usr/lib/go-1.12/src/runtime/lock_futex.go:228 runtime.notetsleepg (0x409064)
       [24 goroutines]
      

      您可以看到,time.Sleep 中有很多 goroutines,而只有一点线程,这些线程正在 runtime.futex 的进程中。

      从Understanding Golang sleep() function和this,我们可以知道golang sleep会涉及到lock,所以这可能是一些额外的线程启动的原因,但是锁很快被释放了,所以那里没有那么多线程,许多 goroutine 仍然可以重用同一个线程。

    2. 对于ioutil.WriteFile,如果我设置启动20个goroutine,接下来是delve的结果:

       ./dlv attach 2455
       Type 'help' for list of commands.
       (dlv) threads
       * Thread 2455 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2456 at 0x4530f3 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
         Thread 2457 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
         Thread 2458 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2459 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2460 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2461 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2462 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2463 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2464 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2465 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2466 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2468 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2469 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2470 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2471 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2472 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
         Thread 2473 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
         Thread 2474 at 0x460e90 /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall
         Thread 2475 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
         Thread 2476 at 0x460f0a /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6
         Thread 2477 at 0x4530f3 /usr/lib/go-1.12/src/runtime/sys_linux_amd64.s:536 runtime.futex
       (dlv) grs
         Goroutine 1 - User: /home/shubuntu1/gotrial/newnew/test.go:24 main.main (0x4754b0)
         Goroutine 2 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
         Goroutine 3 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
         Goroutine 4 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
         Goroutine 5 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
         Goroutine 6 - User: /usr/lib/go-1.12/src/runtime/proc.go:302 runtime.gopark (0x429d9f)
         Goroutine 7 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2457)
         Goroutine 8 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2476)
         Goroutine 9 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2461)
         Goroutine 10 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2473)
         Goroutine 11 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2468)
         Goroutine 12 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2469)
       * Goroutine 13 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2455)
         Goroutine 14 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2459)
         Goroutine 15 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2460)
         Goroutine 16 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2471)
         Goroutine 17 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2464)
         Goroutine 18 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2462)
         Goroutine 19 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2466)
         Goroutine 20 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2474)
         Goroutine 21 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2470)
         Goroutine 22 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2463)
         Goroutine 23 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2472)
         Goroutine 24 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2458)
         Goroutine 25 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:53 syscall.Syscall6 (0x460f0a) (thread 2475)
         Goroutine 26 - User: /usr/lib/go-1.12/src/syscall/asm_linux_amd64.s:27 syscall.Syscall (0x460e90) (thread 2465)
       [26 goroutines]
       (dlv)
      

      可以看到,syscall 的进程中有很多 goroutine,同时syscall 中也有很多线程,不像runtime.futex 中的sleep 只是调用了锁。 ioutil 涉及真正的块系统调用。这是因为 epoll 不适用于 linux 上的常规文件,所以不像网络 io,它具有网络轮询器处理阻塞接口和非阻塞低级 sys 调用,请参阅this。

      所以,我想如果我们不能承受太多线程来进行常规文件操作,那么在应用程序级别,上面的设计是不合理的。也许我们需要一系列专用的file operation goroutines,所有文件相关的需求都应该委托给这些有限数量的带有通道的专用goroutine。

    【讨论】:

      猜你喜欢
      • 2021-12-09
      • 2020-03-18
      • 2017-07-14
      • 2019-11-04
      • 2020-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多