MST

星途 面试题库

面试题:Rust并发场景下复杂的happens - before关系分析

考虑以下Rust代码: ```rust use std::sync::{Arc, Condvar, Mutex}; let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair2 = pair.clone(); std::thread::spawn(move || { let (lock, cvar) = &*pair2; let mut started = lock.lock().unwrap(); *started = true; cvar.notify_one(); }); let (lock, cvar) = &*pair; let mut started = lock.lock().unwrap(); while!*started { started = cvar.wait(started).unwrap(); } ``` 请详细分析在这个多线程场景下的happens - before关系,以及如果改变代码结构(比如调整notify_one和wait的位置)会对happens - before关系产生怎样的影响。
30.4万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

原始代码中的happens - before关系分析

  1. 线程内顺序
    • 在主线程中,let (lock, cvar) = &*pair; 先执行,然后 let mut started = lock.lock().unwrap(); 获取锁,接着进入 while!*started 循环,在循环中调用 cvar.wait(started).unwrap();。这一系列操作在线程内存在先后顺序,前一个操作happens - before后一个操作。
    • 在新创建的线程中,let (lock, cvar) = &*pair2; 先执行,然后 let mut started = lock.lock().unwrap(); 获取锁,接着 *started = true; 修改共享变量,最后 cvar.notify_one(); 唤醒等待的线程。同样在线程内存在先后顺序的happens - before关系。
  2. 线程间的happens - before关系
    • 由于 notify_one() 操作在新线程中修改共享变量 *started = true 之后,并且主线程中 cvar.wait(started).unwrap(); 等待这个通知,所以存在从新线程中 *started = true 到主线程中 while!*started 检查的happens - before关系。这确保了主线程在 while 循环中能够看到新线程对 *started 的修改。具体来说,新线程中获取锁、修改 *started、释放锁并调用 notify_one 这一系列操作,happens - before 主线程中等待通知、获取锁、检查 *started 的操作。

改变代码结构的影响

  1. notify_one 放到修改共享变量之前
    • 线程内顺序依然存在,但是线程间的happens - before关系会改变。此时 notify_one 先执行,然后才修改 *started = true。主线程可能会在 *started 还未被修改为 true 时就收到通知被唤醒,然后检查 *started 依然为 false,又进入等待状态。这样就破坏了原本确保主线程能看到新线程对 *started 修改的happens - before关系,可能导致主线程一直等待,出现逻辑错误。
  2. wait 放到获取锁之前
    • 在主线程中,先调用 wait 会导致程序无法正确同步。因为 wait 操作需要先获取锁(通过 lock.lock().unwrap()),然后释放锁进入等待状态,等待被唤醒后再重新获取锁。如果先调用 wait 而没有获取锁,会导致程序逻辑错误,可能出现死锁或者未定义行为。这种情况下,线程间的happens - before关系也会被破坏,因为无法正确建立共享变量修改和等待唤醒之间的同步关系。