返回题库首页 · 线程控制与进程间通信笔记

先独立作答,再展开参考答案。八股原题沿用原笔记编号;扩展练习为本站根据对应考点编写。

Q16 · fork…join、join_any 和 join_none 有什么区别?

八股原题 · Q16 · 题型:问答

考点

  • 父线程恢复条件
  • 子线程启动时机
  • 未结束线程的处理
展开参考答案 · Q16

题目来源(原笔记记录)

  • 泰凌微 · 数字IC验证 · 校招 · 一面(三类 fork join 语句的区别)
  • 知存科技 · 数字IC验证 · 校招(三种 fork join / fork join_any / fork join_none 的区别)
  • 集益威 · 数字IC验证 · 校招 · 一面(fork join / join_any / join_none 的区别)
  • 达摩院 · 数字IC验证 · 校招 · 一面(三类 fork join 语句的区别)
  • 泰凌微 · 数字IC验证 · 实习(三种 fork 语句的区别)
  • 行云集成电路 · 数字IC验证 · 实习(fork join / join_any / join_none 三者区别)
  • 集益威 · 数字IC验证 · 秋招(fork 系列语句区别)
  • 昆仑芯 · 数字IC验证 · 秋招 · 一面(通信机制、三种 fork)
  • 昆仑芯 · 数字IC验证 · 秋招 · 二面(fork join 线程控制)
  • 面试书《Cracking Digital VLSI Verification Interview》· 第4章 Q57(对应题目:fork-join / fork-join_any / fork-join_none)

join 等全部,join_any 等一个但不杀其余,join_none 一个也不等——区别在父线程什么时候继续。

第一,fork…join。 所有子线程结束,父线程才继续。

第二,fork…join_any。 任意一个子线程完成,父线程就继续;剩余线程还在后台跑,不会自动被杀。

第三,fork…join_none。 父线程立即继续(一个也不等);子线程在父线程遇到阻塞、挂起或结束后才开始调度,所以紧跟其后的零时间代码可能先于子线程执行。

形式父线程何时继续未完成子线程
fork...join所有子线程结束无
fork...join_any任意一个子线程结束继续后台运行
fork...join_none立即继续(不等任何子线程)父线程阻塞/结束后才调度,全部后台运行

一句话:join 等全部,join_any 等一个但不杀其余,join_none 一个也不等;不要把父线程继续和子线程终止混为一谈。


E16 · join_any 后另一分支在哪里?

扩展练习 · E16 · 题型:代码阅读 · 对应原题:Q16

考点

  • 父线程恢复条件
  • 子线程启动时机
  • 未结束线程的处理
timeunit 1ns; timeprecision 1ps;
initial begin
  fork
    #2 $display("A");
    #5 $display("B");
  join_any
  $display("P");
  wait fork;
  $display("D");
end

无其他线程终止仿真时,输出时刻和顺序是什么?

展开参考答案 · E16

2ns 输出 A 后输出 P;5ns 输出 B 后输出 D。join_any 在第一个分支结束后继续父线程,另一分支仍活跃;wait fork 等待剩余直接子进程完成。本题不同分支的完成时刻不同,不涉及同刻分支输出次序的不确定性。


Q17 · wait fork 与 disable fork 分别做什么?

八股原题 · Q17 · 题型:问答

考点

  • 等待直接子进程完成
  • 终止线程作用域
  • guard fork 模式
展开参考答案 · Q17

题目来源(原笔记记录)

  • 泰凌微 · 数字IC验证 · 校招 · 一面(挂起后阻塞等待所有线程结束的实现方式)
  • 达摩院 · 数字IC验证 · 校招 · 一面(终止 fork join_none 创建的进程的方法)
  • 泰凌微 · 数字IC验证 · 实习(fork join 内 while(1) 循环的终止实现)
  • 面试书《Cracking Digital VLSI Verification Interview》· 第4章 Q58(对应题目:wait fork 与 disable fork)

wait fork 是等待所有直接子进程完成,disable fork 是杀掉所有活动后代线程。

第一,wait fork。 阻塞当前线程,直到它创建的所有子线程完成,适合多个 join_none 之后统一收口(等待对象是当前进程的直接子进程,不递归等待已脱离的孙进程)。

第二,disable fork。 终止当前线程的所有活动后代;作用范围要注意——它会杀掉当前线程之前启动的所有后台子线程,不只”本次 fork 里”的,所以可能误杀。

第三,guard fork。 工程上包一层隔离,让 disable fork 只清理竞争线程:

fork begin : guard
  fork
    do_work();
    timeout();
  join_any
  disable fork;
end join

一句话:wait fork 等待直接子进程,disable fork 终止活动后代;用 guard fork 限制误杀范围。

原稿修正:wait fork 等待调用进程的直接子进程;disable fork 终止其活动后代,二者范围不能混写为“全部后代”。


E17 · 超时清理如何避免误杀后台线程?

扩展练习 · E17 · 题型:代码改错 · 对应原题:Q17

考点

  • 等待直接子进程完成
  • 终止线程作用域
  • guard fork 模式

父进程先启动永久心跳,然后 fork 工作和超时、join_any、disable fork。为什么心跳也可能被杀?给出隔离结构。

展开参考答案 · E17

disable fork 会清理调用进程的活动后代,包括先前启动的心跳。让竞争逻辑运行在单独的直接子进程中:

fork
  heartbeat();
join_none
fork
  begin
    fork
      do_work();
      timeout();
    join_any
    disable fork;
  end
join

内层 disable fork 的调用者是隔离子进程,心跳是其兄弟进程。心跳最终结束仍需单独制定策略。


Q18 · 为什么 for 循环里的 fork 经常打印出相同的值?

八股原题 · Q18 · 题型:问答

考点

  • 循环变量共享
  • automatic 快照
  • join_none 延迟启动
展开参考答案 · Q18

题目来源(原笔记记录)

  • 豪威科技 · 数字IC验证 · 校招 · 一面(fork_join_none 代码题,分析输出结果)
  • 沐曦 · 数字IC验证 · 校招 · 笔试(fork-join_none 线程打印输出,缺 automatic 时异常)
  • 面试书《Cracking Digital VLSI Verification Interview》· 第4章 Q60、Q61(对应题目:线程中的共享变量陷阱;fork 内嵌 for 循环产生几个并行进程)

for 循环里 fork 的线程共享同一个循环变量,加上 join_none 延迟启动,最后多个线程打印出同一个最终值。

第一,原因。 线程共享循环变量 i;join_none 子线程要等父线程挂起才运行,那时循环已经结束,所有线程看到的都是 i 的最终值。

第二,解决。 每轮循环创建 automatic 局部变量保存快照:

for (int i = 0; i < 3; i++) begin
  automatic int j = i;     // 每轮进入块时先保存快照
  fork
    $display("%0d", j);
  join_none
end
wait fork;

一句话:fork 分支若直接引用循环变量,读到的是共享变量的后续值;每轮用 automatic 局部变量捕获快照。


E18 · automatic 应在哪一层捕获?

扩展练习 · E18 · 题型:代码阅读 · 对应原题:Q18

考点

  • 循环变量共享
  • automatic 快照
  • join_none 延迟启动
initial begin
  for (int i = 0; i < 3; i++) begin
    fork
      begin
        automatic int j = i;
        $display("%0d", j);
      end
    join_none
  end
  wait fork;
end

能否保证打印 0、1、2?怎样修复?

展开参考答案 · E18

不能。在子进程 begin 块内部初始化发生于子进程启动时,父进程已结束循环,因此这里会读到最终的 i=3。应把 automatic int j = i; 移到每轮循环的 begin 内、fork 之前,再让分支打印 j。修复后值的集合是 0、1、2,输出先后次序不保证。


Q19 · semaphore 的作用是什么?

八股原题 · Q19 · 题型:问答

考点

  • 资源令牌
  • 互斥与有限并发
  • 阻塞和非阻塞获取
展开参考答案 · Q19

题目来源(原笔记记录)

  • 字节跳动 · 数字IC验证 · 实习 · 高频题(旗语 semaphore 的含义与典型使用场景)
  • 昆仑芯 · 数字IC验证 · 校招 · 一面(什么是 semaphore)
  • 集益威 · 数字IC验证 · 校招 · 一面(是否使用过旗语 semaphore)
  • 通用 · 数字IC验证 · 实习 · 基础面经(线程同步方式:event、旗语 semaphore)
  • 面试书《Cracking Digital VLSI Verification Interview》· 第4章 Q66(对应题目:信号量 semaphore)

semaphore 用令牌(key)控制共享资源——1 个 key 是互斥锁,多个 key 是并发配额。

第一,获取。 get(n) 拿 n 个 key,不够就阻塞;try_get(n) 不够时立即返回 0。

第二,归还。 用完必须 put(n) 归还,数量要和获取一致。

第三,典型用法。 new(1) 相当于互斥锁,new(N) 限制最大并发 N 个;常见错误是归还数量不一致、异常路径忘归还,导致其他线程永久阻塞。

semaphore bus_lock = new(1);
 
bus_lock.get(1);
drive_bus();
bus_lock.put(1);

一句话:semaphore 用令牌控制共享资源,1 个 key 是互斥锁,多个 key 是并发配额。


E19 · 令牌数量能自动防止重复归还吗?

扩展练习 · E19 · 题型:场景分析 · 对应原题:Q19

考点

  • 资源令牌
  • 互斥与有限并发
  • 阻塞和非阻塞获取

semaphore s = new(1); 成功 get(1) 后误调用 put(2)。它会像有固定最大容量的锁一样拒绝多余归还吗?后果是什么?

展开参考答案 · E19

不会自动限制为构造时的容量。此时可用 key 变成 2,后续两个各 get(1) 的线程可能同时进入临界区,破坏互斥。应让获取与归还数量配对,并检查提前返回、超时终止等路径是否漏还或重复归还。


Q20 · mailbox 的作用是什么?有界和无界 mailbox 有何区别?

八股原题 · Q20 · 题型:问答

考点

  • 线程安全消息传递
  • 阻塞/非阻塞接口
  • 参数化类型
展开参考答案 · Q20

题目来源(原笔记记录)

  • 小鹏汽车 · IP验证 · 校招(mailbox 的定义与典型使用场景)
  • 国科微 · 数字IC验证 · 实习(driver 如何利用 mailbox 完成 ID 解耦)
  • 面试书《Cracking Digital VLSI Verification Interview》· 第4章 Q67、Q68(对应题目:信箱 mailbox;有界与无界 mailbox)

mailbox 是线程安全的消息队列,常用于 generator 给 driver 传 transaction。

第一,阻塞与非阻塞。 put() 在有界 mailbox 已满时阻塞,get() 在空时阻塞;try_put、try_get、try_peek 是非阻塞方法。

第二,有界 vs 无界。 new(8) 是容量 8 的有界 mailbox,可以对生产者形成背压;不带容量或容量为 0 是无界 mailbox。

mailbox #(packet) mbx = new(8); // 容量 8
mbx.put(pkt);
mbx.get(pkt);

第三,传对象传句柄。 mailbox 传 class transaction 传的是句柄;生产者如果复用并修改同一个对象,消费者会看到被覆盖的数据,所以发送前通常要建新对象或深复制。最好用参数化 mailbox,避免类型不匹配。

一句话:mailbox 可靠传消息,有界版本还能提供背压;传对象时传的是句柄,注意不要复用后篡改。


E20 · mailbox 里的对象会自动复制吗?

扩展练习 · E20 · 题型:代码阅读 · 对应原题:Q20

考点

  • 线程安全消息传递
  • 阻塞/非阻塞接口
  • 参数化类型

一个容量为 2 的 mailbox #(packet),packet 有 int 字段 data。生产者先将同一个 p.data 设为 1 并 put(p),再设为 2 并 put(p);消费者在两次 put 完成后 get 两次。两次读取 data 是什么?

展开参考答案 · E20

两次都为 2,因为两个队列项保存的是同一个对象句柄。容量 2 保证本题生产者不会因队满而提前阻塞,但不提供对象快照。每次发送应创建独立对象并填值;若字段中还有对象句柄,需要按所有权约定做深复制。