设为首页 加入收藏

TOP

goroutine调度(三)
2023-07-23 13:29:03 】 浏览:86
Tags:goroutine 调度
队列中获取G
  • M1获取锁并拿到G1,然后释放锁
  • M3获取锁拿到G2,然后释放锁
  • M2获取锁拿到G3,然后释放锁
  • G1在ch1的channel中阻塞,然后添加到ch1的等待队列。导致M1空闲
  • M1不能闲着,从全局队列获取锁拿到G4,然后释放锁
  • G3阻塞在ch2的channel中,然后被放到ch2的等待队列。导致M2空闲
  • M2获取锁拿到G5,然后释放锁
  • 此时G3在ch2结束阻塞,被放到全局队列尾部等待执行
  • G1在ch1结束阻塞,被放到全局队列尾部等待执行
  • G4,G5,G2执行完成
  • M1,M2,M3重复步骤1-4
    • 互斥锁、定时器和网络 IO 使用相同的机制

    • 如果一个 goroutine 在系统调用中被阻塞,那么情况就不同了,因为我们不知道内核空间发生了什么。 通道是在用户空间中创建的,因此我们可以完全控制它们,但在系统调用的情况下,我们没法控制它们。

    • 阻塞系统调用不仅会阻塞 goroutine 还会阻塞内核线程。

    • 假设一个 goroutine 被安排在一个内核线程上的系统调用,当一个内核线程完成执行时,它将唤醒另一个内核线程(线程重用),该线程将拾取另一个 goroutine 并开始执行它。 这是一个理想的场景,但在实际情况下,我们不知道系统调用将花费多少时间,因此我们不能依赖内核线程来唤醒另一个线程,我们需要一些代码级逻辑来决定何时 在系统调用的情况下唤醒另一个线程。 这个逻辑在 golang 中实现为 runtime·entersyscall()和 runtime·exitsyscall()。 这意味着内核线程的数量可以超过核心的数量。

    • 当对内核进行系统调用时,它有两个关键点,一个是进入时机,另一个是退出时机。

      http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974658.gif

      1. M1,M2试着从全局队列拿G
      2. M1获取锁并拿到G1,然后释放锁
      3. M2获取锁并拿到G2,然后释放锁
      4. M2阻塞在系统调用,没有可用的内核线程,所以go调度器创建一个新的线程M3
      5. M3获取锁并拿到G3,然后释放锁
      6. 此时M2结束阻塞状态,重新把G2放到全局队列(G2由阻塞变为可执行状态)。M2虽然是空闲状态,但是go调度器不会销毁它,而是自旋发现新的可执行的goroutine。
      7. G1,G3执行结束
      8. M1,M3重复步骤1-3

    操作系统可以支持多少内核线程?

    在 Linux 内核中,此参数在文件 /proc/sys/kernel/threads-max 中定义,该文件用于特定内核。

    sh:~$ cat /proc/sys/kernel/threads-max 94751
    这里输出94751表示内核最多可以执行94751个线程

    每个 Go 程序可以支持多少个 goroutine?

    调度中没有内置对 goroutine 数量的限制。

    每个 GO程序 可以支持多少个内核线程?

    默认情况下,运行时将每个程序限制为最多 10,000 个线程。可以通过调用 runtime/debug 包中的 SetMaxThreads 函数来更改此值。

    总结:

    1. 内核线程数可以多于内核数
    2. 轻量级 goroutine
    3. 处理 IO 和系统调用
    4. goroutine并行执行
    5. 不可扩展(所有内核级线程都尝试使用互斥锁访问全局运行队列。因此,由于竞争,这不容易扩展)

    5、M:N 线程分布式运行队列调度器

    为了解决每个线程同时尝试访问互斥锁的可扩展问题,维护每个线程的本地运行队列

    • 每个线程状态(本地运行队列)
    • 仍然有一个全局运行队列
      http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974686.gif
    1. M1,M2,M3,M4扫描本地可运行队列
    2. M1,M2,M3,M4从各自的本地队列取出G4,G6,G1,G3

    从上面的动图可以看到:

    • 从本地队列拿G是不需要加锁的
    • 可运行 goroutine 的全局队列需要锁

    结论:

    1. 轻量级 goroutine
    2. 处理 IO 和 SystemCalls
    3. goroutine 并行执行
    4. 可扩展
    5. 高效

    如果线程数大于内核数,那么会有什么问题呢?

    在分布式运行队列调度中,我们知道每个线程都有自己的本地运行队列,其中包含有关接下来将执行哪个 goroutine 的信息。 同样由于系统调用,线程数会增加,并且大多数时候它们的本地运行队列是空的。 因此,如果线程数大于核心数,则每个线程必须扫描所有线程本地运行队列,并且大部分时间它们是空的,所以如果线程过多,这个过程是耗时的并且解决方案 效率不高,因此我们需要将线程扫描限制为使用 M:P:N 线程模型求解的常数。

    6、M:P: N 线程

    如何检查逻辑处理器的数量?

    package main
    
     import (  
         "fmt"
         "runtime"
     )
    
     func main() {
         fmt.Println(runtime.NumCPU())
     }
    

    分布式 M:P:N 调度例子
    http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974709.gif

    1. M1,M2各自扫描P1,P2的队列
    2. M1,M2从各自的P1,P2中取出G3,G1执行

    在系统调用期间执行P的切换
    http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974724.gif

    1. M1,M2各自扫描P1,P2的队列
    2. M1,M2从各自的P1,P2中取出G3,G1执行
    3. G1即将进入系统调用,所以在这之前G1会唤醒另一个线程M3,并将P2切换到M3
    4. M3扫描P2并取出G2运行
    5. 一旦G1变为非阻塞,它将被推送到全局队列等待运行

    在work-stealing期间,只需要扫描固定数量的队列,因为逻辑处理器的数量是有限的。

    如何选择下一个要运行的 goroutine ?

    Go 调度器 将按以下顺序检查以选择下一个要执行的 goroutine

    • 本地运行队列

      http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974749.gif

    • 全局运行队列

      http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974771.gif

      1. M1,M2,M3各自扫描本地队列P1,P2,P3
      2. M1,M2,M3各自从P1,P2,P3取出G3,G1,G5
      3. G5完成,M3扫描本地队列P3发现空,然后扫描全局队列
      4. M3将从全局队列获取一定数量的G(G6,G7),保存到本地队列P3
      5. 现在M3从本地队列P3取出G6执行
    • Network poller

      http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974790.gif

      1. M1,M2,M3各自扫描本地队列P1,P2,P3
      2. M1,M2,M3各自从P1,P2,P3取出G3,G1,G6
      3. G6执行完成,M3扫描P3发现是空的,然后扫描全局队列
      4. 但是全局队列也是空的,然后就检查网络轮询中已就绪的G
      5. 网络轮询中有一个已就绪的G2,所以M3取出G2并执行
    • Work Stealing

      http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1662974803.gif

      1. M
    首页 上一页 1 2 3 4 下一页 尾页 3/4/4
    】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
    上一篇Golang GMP原理(2) 下一篇分享Go书籍-《Go Web编程》

    最新文章

    热门文章

    Hot 文章

    Python

    C 语言

    C++基础

    大数据基础

    linux编程基础

    C/C++面试题目