bigywater.tech
Rust · 86

Send 与 Sync

Send 和 Sync 是 Rust 并发安全的基石,但名字容易混。这篇不背定义,而是对 22 个常见类型逐个编译验证,再看 Rc、RefCell、裸指针在线程里到底怎么报错,以及为什么一个字段能拖垮整个结构体。

22 个
被逐个编译验证 Send / Sync 的类型数
Send ≠ Sync
Cell/RefCell/Receiver 是 Send 不 Sync;MutexGuard 反过来
E0277
把非 Send / 非 Sync 的值放进线程时的错误码

类型矩阵

实测 · 本机执行输出
  类型                      Send    Sync    
  i32                     ✓       ✓       
  String                  ✓       ✓       
  Vec<u8>                 ✓       ✓       
  &str                    ✓       ✓       
  &mut i32                ✓       ✓       
  Rc<i32>                 ✗       ✗       
  Arc<i32>                ✓       ✓       
  Cell<i32>               ✓       ✗       
  RefCell<i32>            ✓       ✗       
  Mutex<i32>              ✓       ✓       
  RwLock<i32>             ✓       ✓       
  AtomicUsize             ✓       ✓       
  Mutex<Rc<i32>>          ✗       ✗       
  Arc<RefCell<i32>>       ✗       ✗       
  Arc<Mutex<i32>>         ✓       ✓       
  *const i32              ✗       ✗       
  MutexGuard<i32>         ✗       ✓       
  mpsc::Sender<i32>       ✓       ✓       
  mpsc::Receiver<i32>     ✓       ✗       
  Box<dyn Fn()>           ✗       ✗       
  Box<dyn Fn() + Send>    ✓       ✗       
  Weak<i32>(Rc)           ✗       ✗       

Rc 与 Arc 的区别见Arc<T> 与跨线程共享所有权。

Rc 跨线程

实测 · 本机执行输出
  源码:
      1 | use std::rc::Rc; use std::thread;
      2 | fn main() { let r = Rc::new(5); let h = thread::spawn(move || { println!("{}", r); }); h.join().unwrap(); }
  编译:失败
    error[E0277]: `Rc<i32>` cannot be sent between threads safely
       --> <实验目录>/src/u86a.rs:2:55
        |
      2 | fn main() { let r = Rc::new(5); let h = thread::spawn(move || { println!("{}", r); }); h.join().unwrap(); }
        |                                         ------------- -------^^^^^^^^^^^^^^^^^^^^^^^
        |                                         |             |
        |                                         |             `Rc<i32>` cannot be sent between threads safely
        |                                         |             within this `{closure@u86a.rs:2:55}`
        |                                         required by a bound introduced by this call
        |
        = help: within `{closure@u86a.rs:2:55}`, the trait `Send` is not implemented for `Rc<i32>`
    note: required because it's used within this closure
       --> <实验目录>/src/u86a.rs:2:55
        |
      2 | fn main() { let r = Rc::new(5); let h = thread::spawn(move || { println!("{}", r); }); h.join().unwrap(); }
        |                                                       ^^^^^^^
    note: required by a bound in `spawn`
       --> <rust 标准库源码>/library/std/src/thread/functions.rs:128:8
        |
    125 | pub fn spawn<F, T>(f: F) -> JoinHandle<T>
        |        ----- required by a bound in this function
    ...
    128 |     F: Send + 'static,
        |        ^^^^ required by this bound in `spawn`
        = note: the full name for the type has been written to '<实验目录>/bin/u86a.long-type-3231345782421694739.txt'
        = note: consider using `--verbose` to print the full type name to the console
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

Sync 与引用

实测 · 本机执行输出
  源码:
      1 | use std::cell::RefCell; use std::thread;
      2 | fn main() { let c = RefCell::new(1); thread::scope(|s| { s.spawn(|| { *c.borrow_mut() += 1; }); }); println!("{:?}", c); }
  编译:失败
    error[E0277]: `RefCell<i32>` cannot be shared between threads safely
       --> <实验目录>/src/u86b.rs:2:66
        |
      2 | fn main() { let c = RefCell::new(1); thread::scope(|s| { s.spawn(|| { *c.borrow_mut() += 1; }); }); println!("{:?}", c); }
        |                                                            ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `RefCell<i32>` cannot be shared between threads safely
        |                                                            |
        |                                                            required by a bound introduced by this call
        |
        = help: the trait `Sync` is not implemented for `RefCell<i32>`
        = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` instead
        = note: required for `&RefCell<i32>` to implement `Send`
    note: required because it's used within this closure
       --> <实验目录>/src/u86b.rs:2:66
        |
      2 | fn main() { let c = RefCell::new(1); thread::scope(|s| { s.spawn(|| { *c.borrow_mut() += 1; }); }); println!("{:?}", c); }
        |                                                                  ^^
    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`
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.
  编译:成功
  运行(退出码 0):
    5

Mutex 见Mutex<T> 与内部可变性。

裸指针与 unsafe impl

实测 · 本机执行输出
  源码:
      1 | use std::thread;
      2 | struct P(*mut i32);
      3 | fn main() { let mut x = 1; let p = P(&mut x); thread::spawn(move || { let q = p; println!("{:p}", q.0); }).join().unwrap(); }
  编译:失败
    error[E0277]: `*mut i32` cannot be sent between threads safely
       --> <实验目录>/src/u86d.rs:3:61
        |
      3 | fn main() { let mut x = 1; let p = P(&mut x); thread::spawn(move || { let q = p; println!("{:p}", q.0); }).join().unwrap(); }
        |                                               ------------- -------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |                                               |             |
        |                                               |             `*mut i32` cannot be sent between threads safely
        |                                               |             within this `{closure@u86d.rs:3:61}`
        |                                               required by a bound introduced by this call
        |
        = help: within `{closure@u86d.rs:3:61}`, the trait `Send` is not implemented for `*mut i32`
    note: required because it appears within the type `P`
       --> <实验目录>/src/u86d.rs:2:8
        |
      2 | struct P(*mut i32);
        |        ^
    note: required because it's used within this closure
       --> <实验目录>/src/u86d.rs:3:61
        |
      3 | fn main() { let mut x = 1; let p = P(&mut x); thread::spawn(move || { let q = p; println!("{:p}", q.0); }).join().unwrap(); }
        |                                                             ^^^^^^^
    note: required by a bound in `spawn`
       --> <rust 标准库源码>/library/std/src/thread/functions.rs:128:8
        |
    125 | pub fn spawn<F, T>(f: F) -> JoinHandle<T>
        |        ----- required by a bound in this function
    ...
    128 |     F: Send + 'static,
        |        ^^^^ required by this bound in `spawn`
        = note: the full name for the type has been written to '<实验目录>/bin/u86d.long-type-12970848410698637505.txt'
        = note: consider using `--verbose` to print the full type name to the console
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.
  编译:成功
  运行(退出码 0):
    子线程通过 unsafe impl Send 的指针得到 42

unsafe 的边界见unsafe 的安全边界。

自动 trait

实测 · 本机执行输出
  源码:
      1 | use std::rc::Rc;
      2 | struct Ok1 { a: i32, b: String }
      3 | struct Bad1 { a: i32, b: Rc<i32> }
      4 | fn assert_send<T: Send>() {}
      5 | fn main() { assert_send::<Ok1>(); assert_send::<Bad1>(); }
  编译:失败
    error[E0277]: `Rc<i32>` cannot be sent between threads safely
     --> <实验目录>/src/u86f.rs:5:49
      |
    5 | fn main() { assert_send::<Ok1>(); assert_send::<Bad1>(); }
      |                                                 ^^^^ `Rc<i32>` cannot be sent between threads safely
      |
      = help: within `Bad1`, the trait `Send` is not implemented for `Rc<i32>`
    note: required because it appears within the type `Bad1`
     --> <实验目录>/src/u86f.rs:3:8
      |
    3 | struct Bad1 { a: i32, b: Rc<i32> }
      |        ^^^^
    note: required by a bound in `assert_send`
     --> <实验目录>/src/u86f.rs:4:19
      |
    4 | fn assert_send<T: Send>() {}
      |                   ^^^^ required by this bound in `assert_send`
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

分步演示

Send 与 Sync
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。矩阵用 rustc --emit=metadata 试编译 assert 函数得到
    • 没有验证:负向 impl(!Send,不稳定)、PhantomData 对 Send/Sync 的影响、unsafe impl Sync 的使用、async 函数返回的 Future 是否 Send(放在异步部分)、其它平台上 Send/Sync 实现是否不同
    京ICP备2026067502号-1京公网安备11010502063487号