Skip to content

Commit 7c7a02a

Browse files
committed
《深入理解Go语言》读书笔记 - Go 语言协程调度器 GPM 模型
1 parent dfc1b01 commit 7c7a02a

1 file changed

Lines changed: 14 additions & 14 deletions

File tree

source/_posts/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ tags:
3939
> 时间片(Timeslice)又称为“量子(Quantum)”或“处理器片(Processor Slice)”
4040
> 是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是从进程开始运行直到被抢占的时间)。
4141
42-
![多线程多进程执行顺序.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/多线程多进程执行顺序.png)
42+
![多线程多进程执行顺序.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/多线程多进程执行顺序.png)
4343

4444
这是会带来新的问题,当进程越来越多,每个进程又拥有太多的资源时,进程的创建、切换、销毁都会占用很多 CPU 性能。
4545
即进程切换和调度带来的性能消耗可能会超过进程本身的性能消耗,这时候就很不划算了。
4646

47-
![CPU调度切换的成本.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/CPU调度切换的成本.png)
47+
![CPU调度切换的成本.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/CPU调度切换的成本.png)
4848

4949
#### 协程提供 CPU 利用率
5050

@@ -96,7 +96,7 @@ M 想要执行、放回 G 都必须访问全局 G 队列,并且 M 有多个,即
9696

9797
在Go中,线程是运行 Goroutine 的实体,调度器的功能是把可运行的 Goroutine 分配到工作线程上。
9898

99-
![GPM模型.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/GPM模型.png)
99+
![GPM模型.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/GPM模型.png)
100100

101101
1. **全局队列(Global Queue):存放等待运行的 G**
102102
全局队列可能被任意的 P 去获取里面的 G,所以全局队列相当于整个模型中的全局资源,那么自然对于队列的读写操作是要加入互斥动作的。
@@ -185,14 +185,14 @@ M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切
185185

186186
1. 偷取(Work Stealing)机制
187187
当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程。
188-
![偷取机制.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/偷取机制.png)
188+
![偷取机制.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/偷取机制.png)
189189
偷取的动作一定是由 P 发起的,而非 M,因为 P 的数量是固定的,如果一个 M 得不到一个 P,那么这个 M 是没有执行的本地队列的,更谈不上向其他的 P 队列偷取了。
190190
2. 移交(Hand Off)机制
191191
当本线程因为 G 进行系统调用阻塞时,线程会释放绑定的 P,把 P 转移给其他空闲的线程执行,此时若在 M1 的 GPM 组合中,G1 正在被调度,并且已经发生了阻塞,则这个时候就会触发移交的设计机制。
192192
GPM 模型为了更大程度地利用 M 和 P 的性能,不会让一个 P 永远被一个阻塞的 G1 耽误之后的工作,所以遇⻅这种情况的时候,移交机制的设计理念是应该立刻将此时的 P 释放出来。
193-
![移交机制:G1发现阻塞.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/移交机制:G1发现阻塞.png)
193+
![移交机制:G1发现阻塞.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/移交机制G1发现阻塞.png)
194194
为了释放 P,所以将 P 和 M1、G1 分离,M1 由于正在执行当前的 G1,全部的程序栈空间均在 M1 中保存,所以 M1 此时应该与 G1 一同进入阻塞的状态,但是已经被释放的 P 需要跟另一个 M 进行绑定,所以就会选择一个 M3(如果此时没有M3,则会创建一个新的或者唤醒一个正在睡眠的M)进行绑定,这样新的 P 就会继续工作,接收新的 G 或者从其他的队列中实施偷取机制。
195-
![移交机制:阻塞的P被释放.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/移交机制:阻塞的P被释放.png)
195+
![移交机制:阻塞的P被释放.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/移交机制阻塞的P被释放.png)
196196

197197
##### 利用并行
198198

@@ -208,24 +208,24 @@ GOMAXPROCS 也限制了并发的程度,例如 GOMAXPROCS = 核数 / 2,表示最
208208

209209
在新的调度器中依然有全局 G 队列,但功能已经被弱化了,当 M 执行偷取,但从其他 P 偷不到 G 时,它可以从全局 G 队列获取 G。
210210

211-
![全局队列获取.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/全局队列获取.png)
211+
![全局队列获取.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/全局队列获取.png)
212212

213213
#### go func() 调度流程
214214

215215
1. 通过 go func() 创建一个 Goroutine
216-
![go func()创建Goroutine.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/go-func()创建Goroutine.png)
216+
![go func()创建Goroutine.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/go-func()创建Goroutine.png)
217217
2. 有两个存储 G 的队列,一个是局部调度器 P 的本地队列,另一个全局 G 队列。
218218
新创建的 G 会先保存在 P 的本地队列中,如果 P 本地队列满了,则会保存在全局的队列中。
219-
![go func()新建G的放置位置.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/go-func()新建G的放置位置.png)
219+
![go func()新建G的放置位置.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/go-func()新建G的放置位置.png)
220220
3. G 只能运行在 M 中,一个 M 必须持有一个 P,M 与 P 是 1:1 的关系。
221221
M 会从 P 的本地队列弹出一个可执行状态的 G 来执行,如果 P 的本地队列为空,则会从全局队列进行获取,如果从全局队列获取不到,则会向其他的 MP 组合偷取一个可执行的 G 来执行。
222-
![go func()获取G的方式.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/go-func()获取G的方式.png)
222+
![go func()获取G的方式.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/go-func()获取G的方式.png)
223223
4. 一个 M 调度 G 执行的过程是一个循环机制。
224-
![go func()M调度G是循环往复的.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/go-func()M调度G是循环往复的.png)
224+
![go func()M调度G是循环往复的.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/go-func()M调度G是循环往复的.png)
225225
5. 当 M 执行某一个 G 时如果发生了 syscall 或者其余阻塞操作,则 M 会阻塞,如果当前有一些 G 在执行,runtime 则会把这个线程 M 从 P 中移除(Detach),然后创建一个新的操作系统线程(如果有空闲的线程可用就复用空闲线程)来服务于这个 P。
226-
![go func()当M1上的G发生阻塞.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/go-func()当M1上的G发生阻塞.png)
226+
![go func()当M1上的G发生阻塞.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/go-func()当M1上的G发生阻塞.png)
227227
6. 当 M 系统调用结束时,这个 G 会尝试获取一个空闲的 P 执行,并放入这个 P 的本地队列。如果获取不到 P,则这个线程 M 会变成休眠状态,加入空闲线程中,然后这个 G 会被放入全局队列中。
228-
![go func()M1将休眠,G回到全局队列.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/go-func()M1将休眠,G回到全局队列.png)
228+
![go func()M1将休眠,G回到全局队列.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/go-func()M1将休眠,G回到全局队列.png)
229229

230230
#### 调度器的生命周期
231231

@@ -317,7 +317,7 @@ ubuntu@ubuntu:~$ go tool trace trace.out
317317

318318
通过浏览器打开后,点开 view 可以看到:
319319

320-
![trace信息.png](../../images/读书笔记/《深入理解Go语言》读书笔记-Go%20语言协程调度器GPM模型/trace信息.png)
320+
![trace信息.png](https://cooooing.github.io/images/读书笔记/《深入理解Go语言》读书笔记-Go语言协程调度器GPM模型/trace信息.png)
321321

322322
##### Debug trace
323323

0 commit comments

Comments
 (0)