Golang的协程调度器原理

目录

什么是线程?

[🔹N:1 模型](#🔹N:1 模型)

[🔹1:1 模型](#🔹1:1 模型)

⚠️特点

⚠️缺点

✅使用场景

[🔹M:N 模型](#🔹M:N 模型)

⚠️特点

⚠️缺点

✅使用场景

[🔹 直观类比(餐厅例子 🍴)](#🔹 直观类比(餐厅例子 🍴))

什么是进程?

什么是协程?

什么是调度器?

为什么需要调度器?

Goroutine调度器的GMP模型的设计思想

[1. 为什么要有 GMP 模型?](#1. 为什么要有 GMP 模型?)

[2. 设计思想](#2. 设计思想)

(1)减少全局锁竞争

(2)充分利用多核

(3)避免阻塞拖垮全局

[(4)负载均衡(work stealing)](#(4)负载均衡(work stealing))

[3. GMP 的运作机制(简化版流程)](#3. GMP 的运作机制(简化版流程))

[4. 设计优点](#4. 设计优点)


什么是线程?

  • 定义 :线程是操作系统中 CPU 调度的基本单位

  • 特点

    • 一个进程里可以有多个线程(多线程)。

    • 同一进程的线程共享内存空间(代码段、堆、全局变量),但有自己的栈和寄存器。

    • 切换开销比进程小,因为线程之间共享资源,不需要切换整个内存环境。

👉 可以把 线程比作"一栋房子里的人",大家共用厨房、厕所(共享内存),但每个人有自己的卧室(私有栈)。

✅ 一个线程分为"内核态"线程和"用户态"线程

一个"用户态线程"必须要绑定一个"内核态线程",但是CPU并不知道有"用户态线程"的存在,它只知道它运行的是一个"内核态线程"(Linux的PCB进程控制块)。

✅ 所以我们细分一下:内核线程依然叫"线程(thread)"用户线程叫"协程(co-routine)".

🔹N:1 模型

⚠️ N个协程绑定1个线程,优点就是协程在用户态线程即完成切换,不会陷入到内核态,这种切换非常的轻量快速。但也有很大的缺点,1个进程的所有协程都绑定在1个线程上(用户态切换 → 快速、低开销)

⚠️缺点:

  • 某个程序用不了硬件的多核加速能力
  • 一旦某协程阻塞,造成线程阻塞,本进程的其他协程都无法执行了,根本就没有并发的能力了。

🔹1:1 模型

⚠️定义:每一个用户态线程(或协程 API 封装的"线程")对应一个内核线程。

⚠️特点

  • 操作系统直接负责调度。

  • 可以充分利用 多核 CPU(因为多个内核线程可以分布到不同的 CPU 核心上)。

  • 阻塞不会影响其他线程(每个任务独立对应一个内核线程)。

⚠️缺点

  • 内核线程很重

    • 创建/销毁线程需要内核参与,开销大。

    • 内核线程栈通常是 MB 级别内存,不适合大规模并发(成千上万个线程很快耗光内存)。

  • 切换成本高(上下文切换涉及内核态 ↔ 用户态)。

✅使用场景

  • C/C++、Java(原生 Thread)、Python 的 threading → 本质都是 1:1 模型。

🔹M:N 模型

⚠️定义:M 个用户态协程(goroutines、async tasks 等)可以映射到 N 个内核线程上,由运行时调度器决定怎么分配。

⚠️特点

  • 结合了 N:11:1 的优点:

    • 协程轻量,能支持大规模并发。

    • 内核线程少量但可多核利用。

  • 如果某个协程阻塞,调度器可以把其他协程调度到别的线程继续运行。

  • 切换时大部分还是用户态完成,开销低。

⚠️缺点

  • 实现复杂:运行时必须自己写调度器(比如 Go 的 G-M-P 模型)。

  • 需要特别处理系统调用/阻塞操作,否则阻塞会拖垮整个线程。

✅使用场景

  • Go 的 goroutine(典型的 M:N)。

  • Erlang 进程。

  • 早期 Java 的 "green threads"(后来废弃)。

🔹 直观类比(餐厅例子 🍴)

  • N:1:一个服务员(线程)要照顾所有顾客(协程),快是快,但他一旦去厕所(阻塞),全场没人服务。

  • 1:1:每个顾客配一个服务员,很豪华(并行),但成本高。

  • M:N:有 N 个服务员(线程),每个服务员手里有 M 个顾客任务(协程),一旦一个顾客卡住,其他顾客可以被别的服务员服务。

什么是进程?

👉 可以把 进程比作"一栋独立的房子",有自己的水电煤气(资源)。

  • 定义 :进程是操作系统中 资源分配的基本单位

  • 组成:一个进程包含了运行的程序代码、数据(堆/栈)、文件句柄、内存空间等。

  • 特点

    • 每个进程有自己独立的 内存地址空间

    • 进程之间相互隔离,一个进程崩溃不会直接影响另一个。

    • 进程间通信(IPC)需要特殊机制,比如管道、消息队列、共享内存、socket。

什么是协程?

协程(Coroutine) 是一种比线程更轻量级的执行单元,可以看作是"用户态的线程"。

特点:

  • 可挂起/恢复:协程可以在执行过程中主动挂起,把 CPU 让给别人;之后再从挂起的位置恢复执行。

  • 用户态调度:协程的切换通常不依赖操作系统,而是由语言运行时或框架调度。

什么是调度器?

调度器(Scheduler)

在计算机系统里,调度器就是一个 负责分配和安排任务运行的组件

简单来说:

调度器决定 "什么时候、在哪个 CPU(或线程)上、运行哪个任务"。

为什么需要调度器?

多进程/线程时代有了调度器需求

单进程时代不需要调度器

Goroutine调度器的GMP模型的设计思想

1. 为什么要有 GMP 模型?

Go 设计之初的目标是:

  • 提供 简单易用 的并发编程方式(go func() 就能开协程)。

  • 能支持 高并发(百万级 goroutine)。

  • 充分利用多核 CPU,同时避免传统线程池/事件循环的缺点。

早期的调度器只有 G(goroutine)和 M(内核线程) ,所有 goroutine 都放到一个全局队列里 → 会出现 锁竞争严重、性能差

于是 Go 1.1 引入了 P(Processor,逻辑处理器) ,形成了 GMP 三要素

  • G(Goroutine):要执行的任务。

  • M(Machine):内核线程,真正跑在 CPU 上。

  • P(Processor):调度的核心,维护 goroutine 队列,绑定 M 决定执行哪些 G。

2. 设计思想

GMP 模型的设计思想可以概括为几条:

(1)减少全局锁竞争

  • 每个 P 维护一个 本地 goroutine 队列

  • 调度时优先从自己的队列里取 G,不用抢全局锁。

  • 只有本地队列空了才去全局队列/偷别的 P 的任务。

👉 这样避免了多线程频繁争抢全局锁。


(2)充分利用多核

  • P 的数量 = GOMAXPROCS(可由用户设置,默认=CPU 核心数)。

  • 每个 P 绑定一个 M,P 的队列里的 goroutine 就能分布在多个核心上运行。

  • 保证 goroutine 调度能横向扩展。

👉 解决了 N:1 模型无法利用多核 的缺陷。


(3)避免阻塞拖垮全局

  • 如果一个 goroutine 在 M 上做阻塞 syscall(比如 I/O),M 会卡住。

  • Go 的调度器会让 P 把队列转交给别的空闲 M,保证其他 goroutine 继续执行。

  • 阻塞的 M 等系统调用返回时,可以重新加入调度。

👉 避免了 一个阻塞拖死所有协程 的问题。


(4)负载均衡(work stealing)

  • 如果某个 P 的队列空了,就会 随机偷取别的 P 一半的任务

  • 这样避免部分 CPU 核心闲置,而另一些过载。

👉 保证任务分布均衡。

3. GMP 的运作机制(简化版流程)

  1. 开一个 goroutine → 放进某个 P 的队列。

  2. P 从队列里取出一个 G,让绑定的 M(线程)执行它。

  3. 如果 G 阻塞 → 调度器把 P 转移给其他 M,避免卡死。

  4. 如果某个 P 空了 → 去全局队列/其他 P 队列偷任务。

4. 设计优点

  • 轻量调度:大部分调度在用户态完成,快速。

  • 多核利用:P 的数量和 CPU 核心绑定,能高效利用硬件。

  • 避免阻塞:调度器能感知阻塞,把其他 G 转移走。

  • 负载均衡:work stealing 保证多线程公平利用。

  • 透明性 :开发者不需要关心调度,直接写 go func() 就行。

相关推荐
励志不掉头发的内向程序员3 小时前
【Linux系列】让 Vim “跑”起来:实现一个会动的进度条
linux·运维·服务器·开发语言·学习
学习路上_write3 小时前
新版Pycharm添加导入anaconda的python解释器
开发语言·python·pycharm
苏三说技术3 小时前
很多大公司为什么禁止在SpringBoot项目中使用Tomcat?
后端
JaguarJack3 小时前
PHP 开发者必须掌握的基本 Linux 命令
后端·php
光影少年4 小时前
AI大模型开发语言排行
开发语言·人工智能
IT_陈寒4 小时前
Redis性能提升30%的秘密:5个被低估的高级命令实战解析
前端·人工智能·后端
爱和冰阔落4 小时前
【C++list】底层结构、迭代器核心原理与常用接口实现全解析
开发语言·数据结构·c++·list
编程岁月4 小时前
java面试0106-java什么时候会出现i>i+1和i<i-1?
java·开发语言·面试
追逐时光者4 小时前
推荐 4 款基于 .NET 开源、功能强大的文件管理工具,助力高效的整理文件与文件夹!
后端·.net
风象南4 小时前
告别日志“大海捞针”,基于SpringBoot的错误指纹聚类实现
spring boot·后端