hyperf 3.1中协程嵌套时父子协程通过coroutine::getcid()获取当前id,coroutine::getpcid($cid)逐级回溯父id直至根协程(返回0),构建父子链;rpc context因上下文隔离无法跨协程自动传递。

面试官问你Hyperf 3.1中协程嵌套时父子协程如何调度、上下文怎么传递、为什么子协程拿不到父协程设的RPC Context,这不是考记忆,是考你是否真正理解Swoole协程底层和Hyperf上下文封装的耦合逻辑——必须能讲清调度链路与数据隔离边界。
协程ID与父子关系链的本质
第一步:调用Coroutine::getCid()获取当前协程ID,这是所有上下文操作的锚点。
第二步:用Coroutine::getPcid($cid)向上追溯父协程ID,返回0表示已到根协程(即主请求协程);该调用不抛异常,但返回值为0或无效整数时不可继续调用getPcid,否则会无限循环或返回null。
第三步:逐级回溯构建父子链,直到$pcid 为止。Hyperf\Rpc\Context::id()方法正是这样实现的——它不是取当前协程ID,而是<strong>【找到最顶层根协程的ID】</strong>,作为上下文存储的统一键名。
为什么子协程默认读不到父协程设置的RPC Context
Hyperf\Rpc\Context::set()内部实际调用的是Coroutine::setContext($key, $value),但它传入的上下文ID是self::id()返回的根协程ID,而非当前协程ID。
而Coroutine::getContext($cid)只能读取指定$cid下挂载的上下文数组;子协程调用getContext(self::id())时,读的是根协程的上下文——这本身没错;但问题在于:【子协程调用set()时也写入根协程上下文,导致多个子协程并发写同一块内存,产生覆盖或脏写】。
这一步操作起来很简单,直接把文件拖进去就行。但若没意识到写入目标是共享的根上下文,就会误以为“数据丢了”,其实是被别的子协程冲掉了。
三种上下文继承方案对比
方法一:显式透传(官方推荐)
在go(function () { ... })前,先$data = Context::get('user.id'),再在子协程内Context::set('user.id', $data)。安全、可控,适合关键业务字段。
方法二:重写get()逻辑,支持多级查找
先查当前协程上下文→查父协程→一直往上直到根协程。注意:不能无限制递归,必须加深度限制(如5层),否则协程链过长时可能栈溢出。
方法三:使用Hyperf\Context\Context替代Hyperf\Rpc\Context
前者专为通用协程上下文设计,set($key, $value, $cid = null)允许显式指定协程ID;若传null,则自动绑定当前协程ID,天然隔离。这是最轻量且符合协程语义的做法。











