循环
跨线程阻塞
当前,用于跨线程阻塞的接口按如下方式工作:
- 当线程
T1想要阻塞等待另一个线程T2正在执行的查询Q时,它会调用Runtime::try_block_on。这个调用会检查是否存在循环。假设没有检测到循环,它就会阻塞T1,直到T2完成Q。此时T1会被唤醒。不过,我们并不知道执行Q的结果,所以T1现在必须“重试”(retry)。通常这会成功读到缓存值。 - 当
T1被阻塞时,运行时会把它的查询栈(一个Vec)移动到共享依赖图数据结构中。当T1被唤醒时,它会在try_block_on返回前重新取回这份查询栈的所有权。
检测循环
当线程 T1 试图执行查询 Q 时,它会尝试从记忆化表中加载 Q 的值。如果发现一个 InProgress 标记,就表示 Q 当前正在计算中。这说明可能存在循环。接着 T1 会尝试阻塞等待查询 Q:
- 如果
Q也是由T1在计算,那么就存在循环。 - 否则,如果
Q是由另一个线程T2在计算,我们就必须检查T2是否(传递地)阻塞在T1上。如果是,就存在循环。
这两种情况都由 Runtime::try_block_on 在内部处理。检测线程内循环比较容易;为了检测跨线程循环,运行时维护了一张线程之间的依赖 DAG(线程以 RuntimeId 标识)。在把一条边 T1 -> T2(也就是 T1 阻塞等待 T2)加入 DAG 之前,它会先检查从 T2 到 T1 是否已经存在一条路径。如果存在,就说明形成了循环,这条边不能加入(否则 DAG 就不再是无环的)。