小米外包(柯莱特)面试问题
讲述一下Golang的GMP模型
G(Goroutine)M(Machine)P(Processor)
-
模型组件
- G
- 轻量化的用户态线程,由Go程序运行时管理,可以动态扩容
- 创建和切换成本低,高并发场景非常适用
- M
- 对应操作系统线程,由内核调度,执行go代码时一定会绑定一个P
- 数量多于P,Go在运行时动态创建销毁M
- P
- 虚拟处理器,管理着本地G队列,数量由配置决定,默认为CPU核心
- 调度上下文,协调GM资源分配,减少全局锁竞争
- G
-
调度机制
- 队列结构
- 本地队列:每个P会维持一个256的本地队列,新G优先加入当前P队列,如果满了转移一半到全局队列
- 全局队列:所有P共享,用于平衡负载,优先级低于本地队列
- 执行流程
- M绑定P执行G,优先从本地队列、全局队列、网络轮询器及工作窃取来从其他P获取G
- 工作窃取:P的本地队列为空时,随机选择其他P窃取一半G,提高资源利用率
- 阻塞处理
- 系统调用阻塞:M与P解绑,P被其他M获取继续执行。阻塞结束后,G重新加入队列,M缓存备用或者销毁
- 网络IO操作:使用netpoller异步处理,G挂起,IO就绪后由空闲M执行,避免线程阻塞
- 抢占调度
- Go 1.2引入协作式抢占(函数调用时触发),1.14后支持基于信号的抢占式调度,防止G长时间占用CPU。
- 队列结构
-
模型优势
- 资源高利用性:P数量限制为CPU核心数,减少线程竞争;M动态调整应对阻塞
- 低延迟切换:本地队列减少锁竞争,工作窃取和全局队列平衡负载
- 高并发支持:轻量级Goroutine和高效调度机制支持数十万并发
-
总结
GMP模型通过层级化的调度设计,p管理队列,m动态绑定,g灵活切换,结合工作窃取及异步IO处理,实现了高效并发。核心在于减少锁竞争、优化资源利用,并通过抢占机制确保公平性。
什么时候使用线程和协程
-
线程使用场景
- 适用场景
- CPU密集任务:并行计算时,利用多核性能
- 依赖线程的生态: 比如传统的多线程代码
- 低延迟响应:需要操作系统级的优先级调度
- 缺点
- 创建销毁成本高
- 大量线程容易内存耗尽或者竞争问题
- 适用场景
-
协程使用场景
- 适用情况
- I/O密集型任务:高并发网络请求、文件读写或者数据库访问=》web服务器
- 高并发轻量级任务: 需同时处理数千个任务=》聊天服务器、微服务网关
- 资源敏感型应用:内存有限,需避免线程过多=》嵌入式
- 优点
- 创建销毁成本低,用户态调度,栈动态调整
- 天然避免锁竞争,适合异步编程模型
- 适用情况
-
关键对比
特性 线程 协程 调度 操作系统内核调度 用户态调度(由语言运行时管理) 内存占用 较大(默认MB级栈) 极小(默认KB级栈,可动态扩容) 切换开销 高(需内核介入) 极低(无系统调用) 并发规模 数百到数千 数十万到百万级 适用任务 CPU密集型、需并行 I/O密集型、高并发 开发复杂度 高(需处理锁、竞态)