feat: add 23 GMP questions to go-java-concurrency
Deploy Examination / deploy (push) Successful in 4s
Deploy Examination / deploy (push) Successful in 4s
新增题目覆盖 Go GMP 调度模型核心知识点: - 单选 ×10: G状态机、M角色、GOMAXPROCS、netpoller、信号抢占、 runtime.schedule、work-stealing、连续栈、sysmon、channel调度 - 判断 ×5: G状态转换、SIGURG异步抢占、本地队列容量、hand-off、栈保护 - 填空 ×5: GOMAXPROCS默认值、调度顺序、stackguard0、SIGURG、GOMAXPROCS(0) - 代码阅读 ×3: 调度交错、M/P hand-off、无缓冲channel同步 Total: 45 → 68 questions
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"topic": "go-java-concurrency",
|
||||
"type": "true_false",
|
||||
"schema_version": "1.0.0",
|
||||
"generated": "2026-09-09T16:21:56+08:00",
|
||||
"generated": "2026-09-13T00:00:00+08:00",
|
||||
"questions": [
|
||||
{
|
||||
"id": "tf-001",
|
||||
@@ -147,6 +147,86 @@
|
||||
"explanation": "JFR是Oracle JDK和OpenJDK(JEP 171/328/336)内置的生产级监控框架,设计目标是开销低于1%。其核心特点包括:1)基于事件驱动的采样机制,预定义了数百种事件类型(如jdk.CPULoad、jdk.GarbageCollection、jdk.ThreadStart等);2)支持命令行启动(jcmd <pid> JFR.start、jfr start/stop/dump)和编程方式(new Recording().start());3)录制数据以.jfr文件存储,可通过JDK Mission Control(JMC)可视化分析,生成火焰图等;4)从JDK 11起可免费用于生产环境(此前商业特性)。JFR与异步采样profiling配合,能有效定位CPU热点、锁竞争、GC停顿等问题。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "tf-011",
|
||||
"type": "true_false",
|
||||
"difficulty": 3,
|
||||
"tags": [
|
||||
"Go",
|
||||
"GMP",
|
||||
"调度模型",
|
||||
"goroutine"
|
||||
],
|
||||
"question": "在 Go 的 GMP 调度模型中,当一个 G 进入系统调用(_Gsyscall 状态)且该系统调用阻塞时,G 会从 _Gsyscall 直接转换为 _Gwaiting 状态。",
|
||||
"answer": false,
|
||||
"explanation": "这是对 G 状态机的常见误解。当 G 执行系统调用时,它处于 _Gsyscall 状态。如果系统调用阻塞,调度器会执行 hand-off 操作——将 P 从当前 M 上摘下并交给另一个空闲 M(或新建 M)以继续运行其他 G。阻塞的系统调用完成后,G 的状态转换路径是 _Gsyscall → _Grunnable(可运行),而非 _Gwaiting。_Gwaiting 状态用于显式的等待操作,例如 goroutine 阻塞在 channel 接收/发送、sync.Mutex.Lock()、select 无就绪 case 等场景。_Gsyscall 和 _Gwaiting 是两种不同的阻塞语义:前者是操作系统层面的系统调用阻塞,后者是 Go 运行时层面的同步原语阻塞。理解这一区别对于分析 goroutine 的生命周期和 pprof 输出至关重要。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "tf-012",
|
||||
"type": "true_false",
|
||||
"difficulty": 2,
|
||||
"tags": [
|
||||
"Go",
|
||||
"GMP",
|
||||
"调度模型",
|
||||
"goroutine"
|
||||
],
|
||||
"question": "从 Go 1.14 版本开始,Go 运行时引入了基于信号的异步抢占机制,使用的信号是 SIGURG。",
|
||||
"answer": true,
|
||||
"explanation": "Go 1.14 正式引入了基于信号的异步抢占机制(asynchronous preemption),解决了此前协作式抢占(cooperative preemption)的致命缺陷——当 goroutine 没有函数调用(例如执行紧密的纯计算循环)时,调度器无法插入抢占检查点,导致其他 goroutine 被饿死甚至 GC 无法 STW。异步抢占通过向目标 M 发送 SIGURG 信号实现:运行时的 sysmon 后台线程检测到长时间运行的 G 后,向其绑定的 M 发送 SIGURG,M 的信号处理器会在安全点(safe point)暂停当前 G 的执行,将其状态设为 _Grunnable 并放回队列。选择 SIGURG 是因为它在 Linux 上未被标准工具和应用广泛使用,避免与用户信号处理冲突。该机制标志着 Go 从纯协作式调度迈向了混合式调度模型。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "tf-013",
|
||||
"type": "true_false",
|
||||
"difficulty": 2,
|
||||
"tags": [
|
||||
"Go",
|
||||
"GMP",
|
||||
"调度模型",
|
||||
"work-stealing"
|
||||
],
|
||||
"question": "在 Go 的 GMP 模型中,每个 P(Processor)持有的本地运行队列(local run queue)的固定容量为 256 个 G。",
|
||||
"answer": true,
|
||||
"explanation": "Go 运行时中,每个 P 维护一个本地运行队列(local run queue),其实现是一个固定大小为 256 的环形队列(ring buffer)。这个容量在运行时源码中定义为 localRunQueueCap = 256。当一个 P 的本地队列已满时,新创建的 G 或从全局队列获取的 G 不会被放入本地队列,而是会将本地队列中约一半的 G(128个)连同新 G 一起移到全局运行队列(global run queue)中,这一操作称为 runqputslow。这种设计在保证本地队列访问无锁(lock-free)的同时,通过溢出机制避免 G 被丢弃。256 的容量是一个工程权衡——足够大以减少全局队列竞争,又足够小以保持缓存友好性。全局队列则是一个无容量限制的链表结构,使用互斥锁保护。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "tf-014",
|
||||
"type": "true_false",
|
||||
"difficulty": 3,
|
||||
"tags": [
|
||||
"Go",
|
||||
"GMP",
|
||||
"调度模型",
|
||||
"goroutine"
|
||||
],
|
||||
"question": "在 Go 的 GMP 模型中,当一个 M 进入系统调用时,调度器会立即终止该 M 并将其资源回收,以节省操作系统线程资源。",
|
||||
"answer": false,
|
||||
"explanation": "这是错误的理解。当 M 进入系统调用时,Go 调度器执行的是 hand-off(移交)操作,而非终止 M。具体流程如下:M 进入系统调用前,调度器将其绑定的 P 摘下(hand-off),将 P 转交给一个空闲的 M(如果有)或新建一个 M 来接管该 P,确保 P 能继续执行其本地队列中的其他 G。进入系统调用的 M 本身会阻塞在操作系统层面,等待系统调用返回。系统调用完成后,M 尝试获取一个空闲的 P:如果成功则继续运行;如果没有空闲 P,M 会将当前 G 放入全局队列,然后自身进入休眠状态(放入空闲 M 链表),而非被终止。这种设计保证了 P 资源不被浪费,同时 M 可以被复用。M 的数量可能超过 P 的数量(M:N 模型中 N 可大于 M),但受到信号处理等约束限制。",
|
||||
"source": null,
|
||||
"related": []
|
||||
},
|
||||
{
|
||||
"id": "tf-015",
|
||||
"type": "true_false",
|
||||
"difficulty": 4,
|
||||
"tags": [
|
||||
"Go",
|
||||
"GMP",
|
||||
"调度模型",
|
||||
"goroutine"
|
||||
],
|
||||
"question": "在 Go 运行时中,栈增长的触发机制是通过硬件内存保护页(guard page)触发 SIGSEGV 信号来实现的。",
|
||||
"answer": false,
|
||||
"explanation": "Go 的栈增长并不依赖硬件内存保护页和 SIGSEGV 信号。Go 采用的是编译器插桩(compiler instrumentation)的软件检查方式。编译器在每个函数入口处插入栈检查代码(称为 stack bound check 或 prologue),将当前栈指针 SP 与 g 结构体中的 stackguard0 字段进行比较。当 SP 低于 stackguard0 时,说明栈空间不足,运行时会触发 morestack 流程:分配一个更大的栈(通常为当前栈的 2 倍),将旧栈内容完整拷贝到新栈,并更新所有指向旧栈的指针(stack copying / stack growth)。stackguard0 的值在栈空间底部附近,预留了一定的安全余量(stackSmall,通常为 128 字节)。这种方式避免了系统调用开销和信号处理的复杂性,且能精确控制栈增长的时机。Go 1.4 之后统一使用连续栈(contiguous stack)方案,取代了早期的分段栈(segmented stack)。",
|
||||
"source": null,
|
||||
"related": []
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user