bigywater.tech
Rust · 94

数据竞争为什么无法通过编译

“数据竞争无法通过编译”是 Rust 的招牌。这篇用能编译失败的真实例子逐条验证,再用 unsafe 故意造一个数据竞争看后果,最后说明它没有解决的另一类问题:逻辑上的竞态条件。

E0499 / E0502
两个线程同时修改、或一读一写时的借用错误码
4000000 → 999862
unsafe 里没有同步的 4 线程 +1:期望与实际
4 件
库存只有 1 件时,安全代码里的「检查-再-操作」竞态卖出的件数

两个线程同时可变地修改

实测 · 本机执行输出
  源码:
      1 | use std::thread;
      2 | fn main() {
      3 |     let mut counter = 0;
      4 |     thread::scope(|s| {
      5 |         s.spawn(|| { counter += 1; });
      6 |         s.spawn(|| { counter += 1; });
      7 |     });
      8 |     println!("{}", counter);
      9 | }
  编译:失败
    error[E0499]: cannot borrow `counter` as mutable more than once at a time
       --> <实验目录>/src/u94a.rs:6:17
        |
      4 |     thread::scope(|s| {
        |                    - has type `&'1 Scope<'1, '_>`
      5 |         s.spawn(|| { counter += 1; });
        |         -----------------------------
        |         |       |    |
        |         |       |    first borrow occurs due to use of `counter` in closure
        |         |       first mutable borrow occurs here
        |         argument requires that `counter` is borrowed for `'1`
      6 |         s.spawn(|| { counter += 1; });
        |                 ^^   ------- second borrow occurs due to use of `counter` in closure
        |                 |
        |                 second mutable borrow occurs here
        |
    note: requirement that the value outlives `'1` introduced here
       --> <rust 标准库源码>/library/std/src/thread/scoped.rs:203:35
        |
    203 |         F: FnOnce() -> T + Send + 'scope,
        |                                   ^^^^^^
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0499`.

一读一写

实测 · 本机执行输出
  源码:
      1 | use std::thread;
      2 | fn main() {
      3 |     let mut v = vec![1, 2, 3];
      4 |     thread::scope(|s| {
      5 |         s.spawn(|| { println!("{:?}", v); });
      6 |         s.spawn(|| { v.push(4); });
      7 |     });
      8 | }
  编译:失败
    error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
       --> <实验目录>/src/u94b.rs:6:17
        |
      4 |     thread::scope(|s| {
        |                    - has type `&'1 Scope<'1, '_>`
      5 |         s.spawn(|| { println!("{:?}", v); });
        |         ------------------------------------
        |         |       |                     |
        |         |       |                     first borrow occurs due to use of `v` in closure
        |         |       immutable borrow occurs here
        |         argument requires that `v` is borrowed for `'1`
      6 |         s.spawn(|| { v.push(4); });
        |                 ^^   - second borrow occurs due to use of `v` in closure
        |                 |
        |                 mutable borrow occurs here
        |
    note: requirement that the value outlives `'1` introduced here
       --> <rust 标准库源码>/library/std/src/thread/scoped.rs:203:35
        |
    203 |         F: FnOnce() -> T + Send + 'scope,
        |                                   ^^^^^^
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0502`.

合法的并发写法

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    多个线程同时只读 &v:合计 21
    每个线程一个不重叠的 &mut 切片:[1, 1, 2, 2, 3, 3]

Send 与 Sync 的规则见Send 与 Sync。

static mut

实测 · 本机执行输出
  源码:
      1 | static mut COUNTER: u32 = 0;
      2 | fn main() { unsafe { COUNTER += 1; let r = &COUNTER; println!("{}", r); } }
  编译:失败
    error: creating a shared reference to mutable static
     --> <实验目录>/src/u94d.rs:2:44
      |
    2 | fn main() { unsafe { COUNTER += 1; let r = &COUNTER; println!("{}", r); } }
      |                                            ^^^^^^^^ shared reference to mutable static
      |
      = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
      = note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html>
      = note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default
    help: use `&raw const` instead to create a raw pointer
      |
    2 | fn main() { unsafe { COUNTER += 1; let r = &raw const COUNTER; println!("{}", r); } }
      |                                             +++++++++
    
    error: aborting due to 1 previous error

unsafe impl Sync 与闭包捕获

实测 · 本机执行输出
  源码:
      1 | use std::{cell::UnsafeCell, thread};
      2 | struct Racy(UnsafeCell<u64>);
      3 | unsafe impl Sync for Racy {}
      4 | fn main() {
      5 |     let c = Racy(UnsafeCell::new(0));
      6 |     thread::scope(|s| { s.spawn(|| { unsafe { *c.0.get() += 1; } }); });
      7 | }
  编译:失败
    error[E0277]: `UnsafeCell<u64>` cannot be shared between threads safely
       --> <实验目录>/src/u94e.rs:6:33
        |
      6 |     thread::scope(|s| { s.spawn(|| { unsafe { *c.0.get() += 1; } }); });
        |                           ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<u64>` cannot be shared between threads safely
        |                           |
        |                           required by a bound introduced by this call
        |
        = help: the trait `Sync` is not implemented for `UnsafeCell<u64>`
        = note: required for `&UnsafeCell<u64>` to implement `Send`
    note: required because it's used within this closure
       --> <实验目录>/src/u94e.rs:6:33
        |
      6 |     thread::scope(|s| { s.spawn(|| { unsafe { *c.0.get() += 1; } }); });
        |                                 ^^
    note: required by a bound in `Scope::<'scope, 'env>::spawn`
       --> <rust 标准库源码>/library/std/src/thread/scoped.rs:203:28
        |
    201 |     pub fn spawn<F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T>
        |            ----- required by a bound in this associated function
    202 |     where
    203 |         F: FnOnce() -> T + Send + 'scope,
        |                            ^^^^ required by this bound in `Scope::<'scope, 'env>::spawn`
    
    …(共 27 行)

真实的数据竞争

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    期望 4000000,实际 999862(没有同步的并发写,更新丢失)

这是未定义行为,结果只是这台机器、这次运行的现象。

竞态条件

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    库存只有 1 件,却卖出了 4 件;库存值 = -3

原子类型与内存序见原子类型与内存序。

分步演示

数据竞争
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:Miri 或 ThreadSanitizer 对 unsafe 竞争的检测(需要 nightly)、跨 FFI 的竞争、死锁(Rust 不阻止)、unsafe 数据竞争在 debug 构建下的表现
    京ICP备2026067502号-1京公网安备11010502063487号