@@ -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
1011011 . ** 全局队列(Global Queue):存放等待运行的 G** 。
102102 全局队列可能被任意的 P 去获取里面的 G,所以全局队列相当于整个模型中的全局资源,那么自然对于队列的读写操作是要加入互斥动作的。
@@ -185,14 +185,14 @@ M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切
185185
1861861 . 偷取(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 队列偷取了。
1901902 . 移交(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
2152151 . 通过 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)
2172172 . 有两个存储 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)
2202203 . 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)
2232234 . 一个 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)
2252255 . 当 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)
2272276 . 当 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