3737M代表一个工作线程,在M上有一个P和G,P是绑定到M上的,G是通过P的调度获取的,在某一时刻,一个M上只有一个G(g0除外)。在P上拥有一个G队列,里面是已经就绪的G,是可以被调度到线程栈上执行的协程,称为运行队列。
3838
3939### 为什么需要多个P
40-
40+ > | 多个 P 使多个 M 可以同时执行 G,实现真正的并行(而不仅是并发)
41+ > | 多个 P 将 Goroutine 分配到不同的逻辑处理器
41421 . 因为当一个M0被阻塞,P可以转而投奔另外的M1
42432 . 当M0处理完返回时,它必须尝试取得一个context P来运行goroutine,一般情况下,它会从其他的OS线程那里steal偷一个context过来
43443 . 如果没有偷到的话,它就把goroutine放在一个global runqueue里,然后自己就去睡大觉了(放入线程缓存里)。Contexts们也会周期性的检查global runqueue,否则global runqueue上的goroutine永远无法执行。
@@ -52,11 +53,11 @@ M代表一个工作线程,在M上有一个P和G,P是绑定到M上的,G是
5253
5354## ** GMP模型核心组件**
5455
55- | 组件 | 说明 | 特点 |
56- | ------| ------| ------|
57- | ** G (Goroutine)** | 轻量级用户态线程 | - 初始栈大小2KB,可动态扩展<br >- 由Go调度器管理,非OS线程 |
58- | ** M (Machine)** | 操作系统线程(内核线程) | - 真正执行计算的资源<br >- 与P绑定后执行G的代码 |
59- | ** P (Processor)** | 逻辑处理器(上下文) | - 维护本地G队列(LRQ)<br >- 数量由` GOMAXPROCS ` 控制(默认CPU核数) |
56+ | 组件 | 说明 | 特点 |
57+ | ----------------- | ------------------------ | ----------------------------------------------------------------- |
58+ | ** G (Goroutine)** | 轻量级用户态线程 | - 初始栈大小2KB,可动态扩展<br >- 由Go调度器管理,非OS线程 |
59+ | ** M (Machine)** | 操作系统线程(内核线程) | - 真正执行计算的资源<br >- 与P绑定后执行G的代码 |
60+ | ** P (Processor)** | 逻辑处理器(上下文) | - 维护本地G队列(LRQ)<br >- 数量由` GOMAXPROCS ` 控制(默认CPU核数) |
6061
6162---
6263
@@ -82,12 +83,12 @@ M代表一个工作线程,在M上有一个P和G,P是绑定到M上的,G是
8283---
8384
8485## ** GMP设计优势**
85- | 特性 | 说明 | 收益 |
86- | ------| ------| ------|
86+ | 特性 | 说明 | 收益 |
87+ | ----------------- | ------------------------------------- | ------------------ |
8788| ** 复用线程(M)** | 通过P池管理M,避免频繁创建/销毁OS线程 | 减少内核态切换开销 |
88- | ** Work-Stealing** | 空闲P从其他P偷取G | 提高CPU利用率 |
89- | ** Hand Off机制** | 当M阻塞时释放P给其他M使用 | 避免CPU空转 |
90- | ** Netpoller集成** | 基于epoll/kqueue实现非阻塞IO | 高并发IO处理能力 |
89+ | ** Work-Stealing** | 空闲P从其他P偷取G | 提高CPU利用率 |
90+ | ** Hand Off机制** | 当M阻塞时释放P给其他M使用 | 避免CPU空转 |
91+ | ** Netpoller集成** | 基于epoll/kqueue实现非阻塞IO | 高并发IO处理能力 |
9192
9293---
9394
0 commit comments