简述 Goroutine 的调度流程?
Goroutine 是 Go 语言中的轻量级线程,Go 运行时使用调度器来管理 Goroutine 的执行。调度器的设计旨在高效地利用系统资源,并在多个 Goroutine 之间公平地分配 CPU 时间。以下是 Goroutine 调度的简要流程:
-
Goroutine 的创建:当通过
go
关键字启动一个新的 Goroutine 时,运行时系统会为其分配一个新的 Goroutine 结构体(G
),其中包含了该 Goroutine 的栈、程序计数器和其他状态信息。 -
运行队列:每个 P(处理器,
Processor
)拥有一个本地运行队列,存储待运行的 Goroutine。全局运行队列用于存储那些没有立即分配到 P 的 Goroutine。当一个新的 Goroutine 被创建时,它通常会被放入当前 P 的本地队列中。 -
调度循环:
- 每个 P 循环从其本地运行队列中取出 Goroutine 并运行。
- 如果本地队列为空,P 会尝试从全局队列中获取 Goroutine。
- 如果全局队列也为空,P 会尝试从其他 P 的本地队列中窃取一部分 Goroutine 以平衡负载。
-
上下文切换:Goroutine 调度器会在以下情况下执行上下文切换:
- 当前 Goroutine 主动让出 CPU(例如通过调用
runtime.Gosched()
)。 - 当前 Goroutine 阻塞(例如通过等待 I/O 或锁)。
- 定时的抢占调度,确保长时间运行的 Goroutine 不会独占 CPU。
- 当前 Goroutine 主动让出 CPU(例如通过调用
-
抢占调度:Go 1.14 引入了基于信号的抢占机制,允许调度器在必要时中断正在运行的 Goroutine,以便其他 Goroutine 也能获得执行机会。这是通过在 Goroutine 执行的函数序列中插入检查点实现的。
-
M-P-G 模型:Go 使用 M(Machine)、P、G 模型来管理并发执行:
M
代表操作系统线程,负责执行 Go 代P
代表逻辑处理器,管理调度队列,控制可并发执行的上下文。G
代表 Goroutine,实际的并发任务。
通过这些机制,Go 的调度器能够有效地管理大量 Goroutine,从而实现高效的并发执行。
调度的流程状态:
简述 Golang 垃圾回收的机制
简单介绍 GMP 模型以及该模型的优点
Golang 是如何实现 Maps 的?
简述 defer 的执行顺序
有缓存的管道和没有缓存的管道区别是什么?
简述 slice 的底层原理,slice 和数组的区别是什么?
Channels 怎么保证线程安全?
Maps 是线程安全的吗?怎么解决它的并发安全问题?
协程与进程,线程的区别是什么?协程有什么优势?
线程是独立调度的基本单位,进程是资源拥有的基本单位。
线程创建、管理、调度等采用的方式称为线程模型。线程模型一般分为以下三种:
- 内核级线程(Kernel Level Thread)模型
- 由系统内核创建,创建成本高,上下文切换成本高,上下文切换需要硬件支持,如果线程阻塞,其他线程可以继续执行,进程不会阻塞,应用案例:Window Solaris
- 用户级线程(User Level Thread)模型
- 由应用程序创建,创建成本低,上下文切换成本低,上下文切换不需要硬件支持,(因为保存线程状态的过程和调用程序都只是本地过程,如果线程阻塞,整个进程将被阻塞)。即不能利用多处理来发挥并发优势,应用案例:Java thread, POSIX threads
- 两级线程模型,也称混合型线程模型
- 充分吸收上面两种模型的优点,尽量规避缺点。其线程创建在用户空间中完成,线程的调度和同步也在应用程序中进行。一个应用程序中的多个用户级线程被绑定到一些(小于或等于用户级线程的数目)内核级线程上。