线程模型有哪些?为什么 Go Scheduler 需要实现 M:N 的方案?Go Scheduler 由哪些元素构成呢?

参考回答

线程模型 是操作系统或编程语言设计中用来管理线程与硬件资源(如 CPU)之间关系的机制,常见的线程模型有以下三种:

  1. 1:1 模型:每个用户线程对应一个内核线程。
  2. N:1 模型:多个用户线程对应一个内核线程。
  3. M:N 模型:多个用户线程通过少量内核线程运行。

Go 的调度器(Scheduler)使用 M:N 模型,即将大量的 Goroutine 映射到少量的内核线程上执行。这种设计兼顾了高并发性能和系统资源效率。

Go Scheduler 的组成
1. G(Goroutine):表示一个具体的协程,是需要调度的执行单元。
2. M(Machine):表示内核线程,用于实际执行 Goroutine。
3. P(Processor):代表逻辑处理器,负责 Goroutine 的分配和执行,与 M 绑定。


详细讲解与拓展

1. 常见线程模型详解

(1) 1:1 模型
– 每个用户线程直接映射为一个内核线程。
– 优点:线程可以并行利用多核 CPU。
– 缺点:线程切换开销大,资源使用率低。

示例:Java 的线程模型。


(2) N:1 模型
– 所有用户线程都由一个内核线程管理,调度在用户态完成。
– 优点:切换效率高,无需操作系统干预。
– 缺点:线程阻塞会导致所有线程阻塞,无法利用多核 CPU。

示例:早期的 Green Thread。


(3) M:N 模型
– M 个内核线程管理 N 个用户线程。
– 优点:结合了 N:1 和 1:1 的优势。能够利用多核 CPU,且切换开销低。
– 缺点:实现复杂,调度器需要高效管理线程。

示例:Go Scheduler、Erlang VM。


2. 为什么 Go Scheduler 选择 M:N 模型?

Go 语言的设计目标是支持 高并发高性能,而 M:N 模型能够平衡资源利用率和调度效率,原因如下:

  1. 高并发支持
    Goroutine 是用户态线程,启动成本低,占用内存小(初始 2KB 栈),可以轻松支持数百万级并发。

  2. 避免阻塞

    • Goroutine 调度是协作式的,如果某个 Goroutine 阻塞,Go 运行时会将其他 Goroutine 调度到空闲的内核线程运行。
    • 这避免了 N:1 模型的 “一阻塞全阻塞” 问题。
  3. 多核利用
    • M:N 模型允许多个 Goroutine 在多个内核线程上运行。
    • 通过 runtime.GOMAXPROCS 配置,可以有效利用多核 CPU。
  4. 调度效率
    • Goroutine 的上下文切换开销低,仅在用户态完成。
    • 相比 1:1 模型,减少了频繁的内核切换开销。

3. Go Scheduler 的组成

Go 的调度器由以下三部分组成:

  1. G(Goroutine)
    • 表示一个具体的 Goroutine,即需要运行的协程。
    • 每个 G 结构体包含:
      • 执行的函数或任务。
      • 栈指针。
      • 状态信息(如是否处于运行、休眠等)。
      • 指向所属 P 的指针。
  2. M(Machine)
    • 表示内核线程,是运行 Goroutine 的执行载体。
    • 每个 M 绑定一个内核线程,负责执行从 P 中取出的 G。
  3. P(Processor)
    • 表示逻辑处理器,是调度的核心单位。
    • 每个 P 包含一个 Goroutine 队列,负责管理一组 G。
    • P 和 M 一一绑定,每次只能有一个 M 处理 P 中的 Goroutine。
    • 通过 runtime.GOMAXPROCS 可以控制 P 的数量(即最大并行度)。

调度过程:
1. M 通过绑定一个 P 获取 Goroutine 队列中的任务。
2. 如果 P 队列为空,P 会从其他 P 的队列窃取任务(Work Stealing)。
3. 如果某个 Goroutine 阻塞,Go 运行时会创建新的 M 或将任务分配给其他 P。


4. Go 调度器的工作流程

简化版的调度流程如下:
1. 创建 Goroutine 时,将其放入 P 的本地队列。
2. M 从绑定的 P 中取出 Goroutine 执行。
3. 如果 Goroutine 阻塞(如 I/O 操作),M 会将其他任务交还给 P,由 P 重新调度。
4. 如果所有本地任务都已执行,P 会从其他 P 的队列中窃取任务(Work Stealing)。
5. 如果没有任务可运行,M 会阻塞或被回收。


总结

  1. 线程模型
    • 1:1、N:1 和 M:N 是三种常见线程模型。
    • M:N 模型通过将大量用户线程映射到少量内核线程,既支持高并发,又避免阻塞问题。
  2. 为什么 Go 使用 M:N 模型
    • 支持高并发。
    • 避免阻塞,提高资源利用率。
    • 减少切换开销,提高性能。
  3. Go Scheduler 的组成
    • G:Goroutine,任务执行单元。
    • M:Machine,内核线程。
    • P:Processor,逻辑处理器,负责管理 Goroutine 队列。

这种设计使得 Go 可以高效地处理数百万级并发任务,同时充分利用多核 CPU 的性能。

发表评论

后才能评论