Rust in Everything
基于 parking_lot 0.12.5

attempted to use a condition variable with more than one mutex

主线位置:主线 2 的 wait。wait 在桶锁里的 validate 把 Condvar.state 设成这把 mutex 的地址;已经是另一把 mutex 就验证失败,park 不入队、不放锁,直接返回,然后 panic(Condvar)。

flowchart TB
  w2["线程 B:cv.wait(&mut g2)(m2)"] --> v{"桶锁里 validate:state 为空或等于 m2?"}
  v -->|"是"| ok["state = m2,入队,before_sleep 放开 m2,睡"]
  v -->|"否:state = m1(A 正在等)"| bad["bad_mutex = true,park 返回 Invalid"]
  bad --> p["panic!,此时 B 仍持有 m2"]
  p --> uw["unwind:守卫 drop,m2 被放开"]

报错原文

E15(exp-parking_lot/condvar-two-mutexes,功能实验):线程 A 用 m1 在 cv 上等;50 ms 后线程 B 用 m2 调 cv.wait。每种 3 次:

parking_lotstd(本机,pthread)
同时等panic「attempted to use a condition variable with more than one mutex」,wait 调用后 63–588 µs;panic 时 m2 仍被持有,B 退出后已解锁panic「attempted to use a condition variable with two mutexes」,50–125 µs;m2 被毒化
先后(A 被通知并退出后,B 再用 m2 等)正常仍然 panic,同一句

两点和直觉不同:

  • panic 发生在第二个等待者调用 wait 时,不是「醒来以后」:几十到几百微秒,说明根本没有睡下。第一个等待者 A 不受影响。
  • parking_lot 只禁止「同时」,std 在 macOS 上禁止「换过」。

源码出处

condvar.rs:296-350,wait_until_internal 的 validate:

let lock_addr = mutex as *const _ as *mut _;
let validate = || {
    // Ensure we don't use two different mutexes with the same
    // Condvar at the same time. This is done while locked to
    // avoid races with notify_one
    let state = self.state.load(Ordering::Relaxed);
    if state.is_null() {
        self.state.store(lock_addr, Ordering::Relaxed);
    } else if state != lock_addr {
        bad_mutex = true;
        return false;
    }
    true
};
// ... park(addr, validate, before_sleep = mutex.unlock(), ...)

// Panic if we tried to use multiple mutexes with a Condvar. Note
// that at this point the MutexGuard is still locked. It will be
// unlocked by the unwinding logic.
if bad_mutex {
    panic!("attempted to use a condition variable with more than one mutex");
}

state 在最后一个等待者离开时(notify_* 的回调、超时退出)清空,所以「没有等待者之后换一把锁」是允许的,文档在 41-46 行 写明了。

std 在 macOS 上用 pthread 条件变量,condvar/pthread.rs:32-41 用 compare_exchange(0, addr) 记下第一次用到的 mutex,之后从不清零。Linux 上 std 用 futex 版条件变量,本卷没有核实它是否检查。

怎么改

  • 一个条件变量只配一把锁,把 (Mutex<State>, Condvar) 放在同一个结构体里一起传,从类型上避免拿错。
  • 不同的条件用不同的 Condvar,同一把锁可以配多个条件变量(「非空」「非满」各一个),反过来不行。
  • 确实要在不同时段换锁:parking_lot 允许,但要保证上一批等待者全部离开;本机 std 不允许,从 parking_lot 换到 std 时要留意。
  • panic 时守卫在 unwind 中释放,parking_lot 不毒化,所以 m2 之后还能正常用;std 的 m2 会被毒化。

小结

  • parking_lot:两把 mutex 同时在一个 Condvar 上等,第二个 wait 调用时立即 panic「attempted to use a condition variable with more than one mutex」。
  • 检查在桶锁里的 validate,失败时不入队、不放锁,panic 的 unwind 再放锁。
  • 没有等待者之后可以换锁;本机 std 换过就 panic,报错是「with two mutexes」。
  • 一个条件变量配一把锁,按条件拆分条件变量。

💬 关联讨论

发起讨论
加载中...