“要从r+1推进到r+2,推进者必须读取每个公告,接受的只有inactive或r+1。一个仍持有n的旧读者不满足这个条件。若扫描看见它inactive,则该线程已经在程序顺序上结束此前全部使用…”
形式陈述
固定一次合法执行的事件集合 unlock 与随后成功的 lock 配对。具体模型可以只采用其中一部分,不能把未由规范声明的墙上时间或缓存传播自行加边。
令
Happens-before 关系
合法执行要求这些生成边无环,因此
才是偏序的非严格形式。对两个不同事件
则称它们在该模型的 happens-before 意义下并发。这个定义只说二者不可比较,不保证其内存效果可交换。
直觉
Happens-before 记录模型允许的信息影响路径。程序顺序让同一线程的前一步有机会影响后一步,消息接收携带发送之前的信息,同步操作把一个线程先前的写入交给另一个线程。传递闭包再把这些局部证据串成跨线程、跨节点的因果链。存在路径不要求后一个事件真的在业务上使用了前一个结果;即使接收者丢弃消息,发送—接收边仍存在。
判断关系时可先画基本边,再沿箭头寻找路径;找得到从
它不是物理时间轴。两个事件即使在测量上先后相隔很久,只要模型没有程序、通信或同步路径,仍可能不可比;反过来,某些语言允许编译器重排机器指令,却仍要求抽象执行保持规定的 happens-before。分析必须始终说明关系属于哪一层。
例子与边界
消息执行中,进程
故
共享内存中,线程 data = 42,再以 release 语义写 ready = true;线程 ready 写取得 true,便有
所以数据写 happens-before 数据读。若 acquire 读没有观察该 release 写,或 ready 只是普通变量,所需 synchronizes-with 边可能不存在;仅凭读到了数值 true 不能跨语言模型自行补边。
发布例还要求 ready 是模型认可的原子同步对象,且载荷读写没有其他未排序的冲突访问。如果另一个线程在发布后继续无同步改写 data,上面的四条边并不会顺便排序这次新写;一次发布只保障对应的既有信息,不会永久冻结对象。
若发布的是替代旧配置的新指针,发布正确还不足以立即释放旧配置。RCU把发布与回收分成两步:先使后续读者能取得新版本,再等覆盖原有读区间的宽限期结束。仍持有旧指针的读者何时退出,是与新版本可见性不同的证明义务。
语言模型还可能规定单个原子位置的 modification order、Java 同步动作的 synchronization order,或所有顺序一致原子的某个总序。这些附加次序不等于 happens-before 本身;只有规范指定的边才参与生成
推论与应用
Lamport 时钟保证
数据竞争用模型特定的 happens-before 判断冲突普通访问是否未排序;不可比的只读访问却不会因此成为 race。偏序约简还要求动作独立与交换,不能仅从两个事件无因果路径就推出交换执行保持所有观察。
参考资料
- Leslie Lamport, “Time, Clocks, and the Ordering of Events in a Distributed System,” Communications of the ACM 21(7), 1978, pp. 558–565。
- Nancy A. Lynch, Distributed Algorithms, Morgan Kaufmann, 1996,§6.2。
- Hans-J. Boehm and Sarita V. Adve, “Foundations of the C++ Concurrency Memory Model,” PLDI 2008, pp. 68–78。