协程与异步运行时(C++20/Lua/结构化并发/IO 后端)

共 35 题
#

1. C++20 coroutine 在无栈(stackless)与有栈(stackful)两种 ABI 的工程差异?

A 有栈协程的帧布局也由编译器私有
B C++20 协程使用独立栈
C 无栈协程可以挂起普通函数调用
D C++20 无栈协程以编译期帧对象保存状态,挂起仅限协程内 await 点且帧布局编译器私有;有栈协程以独立栈+寄存器切换,任意调用链可挂起、ABI 兼容好 ✓ 正确答案
#

2. boost::fiber 与 Go goroutine 在抢占与栈管理的工程差异?

A Go 的 goroutine 没有抢占
B boost::fiber 支持自动栈增长
C boost::fiber 是协作式(不主动让出即独占线程)且固定栈(需按最坏深度配置);Go 有信号级异步抢占与动态增长栈,G-M-P 全局调度可迁移 ✓ 正确答案
D fiber 可以跨线程自动迁移
#

3. libaco / libmill / libgo 在 1MB 协程栈的工程边界?

A 共享栈不需要保存栈内容
B 1MB 栈可支持百万协程无压力
C 固定栈永不溢出
D 1MB 独立栈的边界是"任务数×栈 vma/内存"(万级以上受限)、深递归溢出与切换缓存;共享栈以拷贝活跃栈换省映射,适合浅栈海量任务 ✓ 正确答案
#

4. C++20 coroutine 的三个关键字 co_await / co_yield / co_return 的语义边界?

A co_await 挂起等待外部异步操作(await_ready/suspend/resume 三阶段),co_yield 产出值后挂起供消费方恢复(生成器),co_return 以值或无值结束协程 ✓ 正确答案
B co_yield 等价于普通 return
C co_await 只用于同步等待
D 协程内可以使用普通 return
#

5. co_await 表达式 awaiter.await_ready / await_suspend / await_resume 三 stage 协议?

A await_ready 永远返回 false
B await_ready=true 时零挂起继续,await_suspend 以 void/bool/句柄决定挂起或对称转移(不能在其中直接 resume 自身),await_resume 在恢复后返回结果或抛异常 ✓ 正确答案
C await_suspend 可以直接 resume 当前协程
D await_resume 在挂起前调用
#

6. C++20 coroutine 中 coroutine_handle 与 promise_type 的生命周期,谁负责在结束时调用 destroy,final_suspend 的作用是什么?

A final_suspend 只用于调试
B 协程可以自己 destroy 自己
C 帧由创建者分配、结束时经 final_suspend:suspend_never 自动销毁,suspend_always 由等待者显式 destroy(RAII 封装);忘记 destroy 即帧泄漏 ✓ 正确答案
D 帧在 co_return 时自动释放
#

7. 协程栈的可动态扩展(growing stack)vs fixed stack 的内存效率?

A 动态栈不需要指针重写
B 动态栈按最坏深度预分配
C 固定栈有扩容抖动
D 固定栈按配置大小分配(深任务需预算、浅任务虚拟浪费、无增长开销),动态栈小起步按需扩容(拷贝重写指针)按实际使用占用内存,适合浅栈海量任务 ✓ 正确答案
#

8. Rust 协程与 stackless coroutine 在栈管理的工程差异?

A Rust async 使用独立协程栈
B Rust 无栈模型把跨 await 的栈变量提升进 future 状态对象(无协程栈可溢出),但自引用状态需 Pin 固定地址、递归 async 需 Box 间接,状态大小决定栈/堆放置 ✓ 正确答案
C future 移动后自引用仍然安全
D 递归 async fn 直接可编译
#

9. Tokio 与 Go runtime 如何将协程/纤程挂接到 epoll、kqueue 或 io_uring 事件循环上调度?

A 阻塞系统调用可以直接在事件循环中执行
B Go 使用用户态 waker 层
C Tokio 的 reactor 不关联任务
D Tokio 经 mio(epoll/kqueue/io_uring)注册 fd 兴趣,就绪后经 waker 把任务推入 worker 队列轮询;Go 的 netpoller 直接把等待的 G 置为 runnable 入队,二者都是"事件就绪→任务唤醒" ✓ 正确答案
#

10. Go goroutine 初始栈约 2KB 与动态增长,栈复制(stack copying)如何移动指针并更新引用,为什么增长不需要 guard page?

A Go 栈增长=分配新栈逐帧拷贝并依栈映射重写栈内指针(精确 GC 位图);溢出检测是序言软件检查(morestack)而非 guard page 硬件页错误,栈是可增长对象 ✓ 正确答案
B Go 栈使用 guard page 检测溢出
C 栈复制不需要指针信息
D Go 栈大小固定 2KB
#

11. Lua 5.x 的协程(coroutine.create / coroutine.yield / coroutine.resume)state machine 模型?

A Lua 协程是对称转移模型
B Lua 协程是非对称有栈模型:resume 启动/恢复、yield 挂起回传值,状态为 suspended/running/normal/dead,每协程独立 lua_State;yield 只能回到 resume 自己的调用者 ✓ 正确答案
C dead 协程可以被重新 resume
D yield 可以直接转移给任意协程
#

12. Lua yield 在 C API 边界恢复的 yield-aware C function 工程价值?

A C 函数帧无法被 Lua 保存,直接跨 C 边界 yield 报错;yield-aware C 函数用 lua_yieldk 挂起、恢复时经 continuation 继续,使阻塞 C 库可协程化 ✓ 正确答案
B C 函数中可以随意 yield
C continuation 自动保存 C 局部变量
D lua_callk 与 yield 无关
#

13. libmill 的 go 关键字与 mill_defer 类 Go 语法的工程价值?

A go 宏创建 OS 线程
B mill_defer 在协程启动时执行
C libmill 的 go 以宏启动协程(类 Go 异步任务),mill_defer 注册协程退出时执行的清理(类 Go defer),消除错误分支手写清理;受宏作用域与协作式调度限制 ✓ 正确答案
D libmill 是抢占式调度
#

14. C++20 coroutine 的对称(symmetric transfer)相对 asymmetric transfer 在 scheduler 的工程价值?

A 非对称转移的链式 resume 使调用栈随链长增长(深链爆栈),对称转移(await_suspend 返回 handle)以尾调用式平级跳转保持栈深恒定,适合调度器任务链 ✓ 正确答案
B 对称转移也叠加调用栈
C await_suspend 返回 void 即对称转移
D 对称转移只影响性能不影响正确性
#

15. cppcoro libunifex asio::experimental::coro 的工程库差异?

A 三者都是网络库
B cppcoro 提供 task/generator 等协程类型与异步同步原语,libunifex 是 sender/receiver 执行模型(组合子+执行上下文),asio::experimental::coro 与 io_context 深度绑定服务网络场景 ✓ 正确答案
C libunifex 只提供生成器
D asio coro 不依赖 io_context
#

16. Lua asymmetric coroutine 在 Lua VM 的 stack switch 工程?

A Lua 协程切换是解释器状态交换(保存/恢复 lua_State 的 Lua 栈、CallInfo 与 PC),无机器栈切换与栈增长问题;但 C 函数边界不能直接跨过 yield ✓ 正确答案
B Lua 协程切换需要汇编级寄存器保存
C 每协程有独立机器栈
D Lua 协程可在任意 C 调用中挂起
#

17. boost::fiber 的 symmetric coroutine 在 shared stack 切换的工程价值?

A 共享栈上数据跨切换仍可安全引用
B boost::fiber 是非对称 resume/yield 模型
C 共享栈切换是寄存器级
D boost::fiber 是对称协程(任意 fiber 间直接转移);共享栈让多 fiber 复用机器栈、切换时拷贝保存/装载活跃栈,省内存但深栈拷贝贵且栈上指针跨切换失效 ✓ 正确答案
#

18. 对称转移(symmetric transfer)如何避免协程之间无限 resume 导致的调用栈增长,与普通递归调用栈的增长差异?

A 对称转移的栈深随链长增长
B 非对称的链式 resume 在调用栈上嵌套(深度=链长,无限链爆栈);对称转移把恢复实现为尾调用式平级跳转(帧在协程状态中),栈深恒定 O(1) ✓ 正确答案
C 递归与对称转移的栈增长相同
D 对称转移只适合单层 await
#

19. Lua 的协程本质是独立 lua_State 线程,resume/yield 时 Lua 栈如何切换,C 函数中 yield 的限制与 lua_yieldk 的作用如何?

A lua_yieldk 让 C 函数自动保存局部变量
B 协程是独立 OS 线程
C C 函数可以直接 yield
D Lua 协程是独立 lua_State(解释器线程),resume/yield 切换解释器状态(Lua 栈/CallInfo/PC);C 函数跨边界 yield 需 lua_yieldk 挂起并注册 continuation 恢复 ✓ 正确答案
#

20. work-stealing 调度器如何与底层 I/O 多路复用协作,空闲工作线程如何窃取任务?

A I/O 就绪经 waker 把任务推入 worker 队列;worker 本地空则窃取其他队列、再空则进全局队列、全空则阻塞于 epoll_wait 等待事件(eventfd 可唤醒) ✓ 正确答案
B 空闲 worker 忙轮询 I/O
C I/O 事件与任务队列无关
D 窃取只在本地队列满时发生
#

21. 协程在阻塞系统调用上为何会拖垮整个工作线程,运行时如何用非阻塞封装规避?

A 阻塞只影响发起协程
B 协程内阻塞系统调用使整个 OS 线程睡眠、全部协程停摆;规避靠非阻塞封装(O_NONBLOCK+epoll)、异步 I/O(io_uring)与阻塞线程池外派 ✓ 正确答案
C 事件循环可以容忍阻塞调用
D 同步文件 IO 在协程内是安全的
#

22. 用户态协程的"信号化"抢占,如何在 syscall boundary / timer tick 通过 SIGURG SIGVTALRM 抢占?

A 信号可以在任意指令处安全打断
B 信号化抢占由 timer tick 或调度器主动发信号(Go 用 SIGURG),处理程序在安全点注入让出;阻塞 syscall 无法信号打断,靠让渡 P 处理 ✓ 正确答案
C SIGVTALRM 按墙钟时间触发
D syscall 中可通过信号抢占
#

23. io_uring 相比 epoll 在协程运行时中如何减少系统调用次数与上下文切换开销?

A epoll 天然支持异步 IO 语义
B epoll 与 io_uring 每次 IO 都需两次系统调用
C io_uring 每次只能处理一个 IO
D io_uring 通过共享内存提交/完成队列一次批量处理多个 IO,减少系统调用与上下文切换;SQPOLL 模式甚至可省去 io_uring_enter ✓ 正确答案
#

24. Go runtime 的 netpoller 如何将 goroutine 的阻塞 I/O 转化为基于 epoll 的就绪通知?

A netpoller 不依赖 epoll 库
B netpoller 让每个 goroutine 独立阻塞一个系统调用
C epoll 就绪后直接在内核态执行 goroutine
D netpoller 用 O_NONBLOCK fd 在 EAGAIN 时挂起 goroutine 并注册到 epoll,就绪后按 fd→g 映射唤醒,用户看到阻塞式语义 ✓ 正确答案
#

25. Windows IOCP 与 Linux epoll 在完成通知模型上的差异如何影响协程运行时设计?

A epoll 完成事件可被任意线程并发取回
B IOCP 与 epoll 都是就绪通知模型
C epoll 回报就绪后仍需用户二次发起 IO;IOCP 一次提交即完成数据搬移并按完成事件通知,协程运行时挂起/恢复点不同 ✓ 正确答案
D IOCP 需要用户再次发起数据搬移
#

26. 协程栈的"guard page"在栈溢出时的 SIGSEGV 与 OS thread 栈的工程差异?

A OS thread 栈用 guard page 触发 SIGSEGV 检测溢出;协程固定栈也可用 guard page 但每协程多占一页、放大页表压力,Go 动态栈改用栈拷贝探测 ✓ 正确答案
B 协程栈不能使用 guard page
C Go 动态栈依赖 guard page 检测溢出
D guard page 不占用任何地址空间
#

27. 协程栈管理,有栈协程 vs 无栈协程的切换开销如何对比?

A 无栈协程可任意深度挂起
B 无栈协程切换开销比有栈大
C 有栈协程不占用独立栈
D 有栈协程切换需完整上下文切换、开销大但可任意嵌套调用;无栈协程用状态机 frame、切换轻量但挂起点受限 ✓ 正确答案
#

28. 事件循环调度的公平性,长时间运行的协程为何会饿死其他协程,运行时通过抢占与调度配额如何解决?

A 协作式调度天然公平,无需抢占
B 协作式调度下长任务不主动让出会饿死其他协程;运行时用抢占(检查点/信号)与调度配额(时间片)强制轮流执行 ✓ 正确答案
C 长任务只饿死自己,不影响他人
D 抢占不需编译器/安全点支持
#

29. Kotlin 结构化并发的取消传播,父协程取消时子协程如何收到 CancellationException,与线程池 shutdownNow 的差异如何?

A 子协程无法响应父协程取消
B 取消会立即强制终止子协程
C shutdownNow 沿父协程树传播取消
D 结构化并发下父协程取消沿协程树传播到子协程,子协程在挂起点抛 CancellationException 响应;与线程池 shutdownNow 的中断式、无层级传播不同 ✓ 正确答案
#

30. libaco 的"独立栈"(separate stack)与"共享栈"(shared stack)切换开销?

A 共享栈切换无需拷贝
B 独立栈切换开销比共享栈大
C 独立栈切换只交换栈指针、无拷贝但内存按协程增长;共享栈切换需拷贝栈内容、省内存但开销随栈深度增加 ✓ 正确答案
D 独立栈内存占用固定
#

31. boost::fiber 的 scheduler work-stealing 与 fiber_local 数据的工程价值?

A work-stealing 让空闲线程窃取其他线程 fiber 队列实现负载均衡;fiber_local 按 fiber 隔离存储,切换时随 fiber 切换、区别于 thread_local ✓ 正确答案
B fiber_local 与 thread_local 等价
C work-stealing 只在单线程可用
D fiber_local 随 OS 线程切换
#

32. C++20 协程,co_await/co_yield 的机制与性能开销如何?

A C++20 协程是有栈协程
B C++20 协程是无栈协程,co_await 经 await_ready/await_suspend/await_resume 三阶段挂起恢复,co_yield 产出值;主要开销是 frame 分配与轻量挂起/恢复 ✓ 正确答案
C co_await 需完整上下文切换
D co_yield 只挂起不产出值
#

33. Go async preemption 的 preempt(M=preempt)状态在 runtime 的工程价值?

A Go 1.14 前已有异步抢占
B 异步抢占可在任意指令处打断
C async preemption 用 SIGURG 信号在安全点把超时 goroutine 标记为 preempt 并让出,解决长任务卡死 P、保证 GC 推进与公平调度 ✓ 正确答案
D 抢占只针对 syscall 阻塞
#

34. 结构化并发(structured concurrency,如 JEP 462、Kotlin)的取消传播语义如何保证子任务不泄漏?

A 取消传播不强求子任务响应
B 子任务与父生命周期无关
C 父作用域可直接返回、子任务继续运行
D 结构化并发把子任务绑定在父作用域内,父取消时级联取消子任务并等待其完成,防止子任务泄漏到父作用域之外 ✓ 正确答案
#

35. 用户态调度与 IO 后端,epoll/io_uring 驱动的协程调度如何实现?

A 协程必须每协程一个内核线程
B 用户态调度器在队列中调度协程,epoll 以就绪事件、io_uring 以完成事件+批量提交唤醒协程,二者结合构成异步运行时核心 ✓ 正确答案
C io_uring 与 epoll 一样只做就绪通知
D IO 事件与协程调度无关