前情
- 我们的技术总监在我写广告合并请求的业务时, 和我说了一句现在的服务是不是都是运行在
0.5核
的pod
上, 需要注意设置一下参数
- 然后我回去看了一下, 我们的
golang
部分服务是运行在k8s
上0.5核
的pod
, 然后跑在多台8核
的物理节点上
- 然后程序中可以通过以下命令打印出当前的
GOMAXPROCS
, 服务虽然运行在pod
上,但打印的是实际的宿主机的核心数
package main
import (
"fmt"
"runtime"
)
func main() {
// runtime.GOMAXPROCS(1) : 设置为 1
fmt.Printf("当前: %d", runtime.GOMAXPROCS(0))
// 8
}
why
GMP
设计思想
G
代表goroutine
协程,M
代表machine
线程,P
代表processor
处理器;P
包含了运行G
所需要的资源,M
想要运行goroutine
必须先获取P
- 为什么修改
GOMAXPROCS
参数可以更高效的运行呢? 简单的来说, 就是本身容器只有0.5
核, 但是却设置了GOMAXPROCS=8
, 导致会创建出8
个P
, 每个P
由不同的M
管理
- 所以当
GOMAXPROCS
大于核心数量的时候, 会导致线程不断的切换, 然后cpu
有一部分时间被切换占用了(设置为cpu
的核心数可以减少切换, 但还是会有切换的场景)
Q 那么设置GOMAXPROCS
等于1
的时候, 什么时候会出现线程切换? 是不是无法高并发呢?
- 答案是可以的, 虽然同是只能处理
1
个任务, 但是cpu
实在是太快了. 并且网络io
(大部分web
服务都是属于网络io
)不占用cpu
时间
- 根据
G-P-M
调度模型, GOMAXPROCS
等于1
,
- 一个
P
指为P1
运行(假设P1
里的本地队列有G0
,G1
,G2
三个goroutine
),
- 创建新的
M1
绑定到P1
上
- 当
G0
里遇到以下场景
- 出现系统调用, 文件
io
阻塞的时候
- 会把当前的线程
P
绑定的M1
线程去交给系统调度,
- 然后从休眠线程队列/创建新的线程
M2
- 然后绑定到
P1
, 继续调度P1
后面的G1,G2
- 当
M1
的阻塞调用结束后, 会优先返回P1
, 但是P1
已经被M2
占用, 然后从空闲队列P
获取,但是我们只有一个P
,也获取失败, 就会把G0
放到全局队列中, 等待P1
之后获取
PS
- 网络
io
不会造成阻塞, 因为golang
在网络请求时,其后台通过kqueue(MacOS),epoll(Linux)或 iocp(Windows
来实现IO
多路复用
- 所以网络
io
密集的服务, 即使核心数量不多, golang
也能处理高并发请求