You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何说被Mutex包裹的所有内容并非都是线程安全的?

加锁仍可能丧失内存安全性的情况

你的核心疑问是:既然锁能保证同一时间只有一个线程访问内部数据,为何Rust的Mutex<T>还要求T满足Send或Sync才能让自身成为Send或Sync?答案是确实存在加锁后仍会破坏内存安全的场景,Rust的这些约束正是为了从根源避免这类问题,以下是具体说明:

1. 内部类型不满足Send时的风险

Send标记表示类型可以安全地跨线程转移所有权。如果T不满足Send(比如Rc<u32>这类单线程引用计数类型),即使把它放进Mutex,跨线程传递这个Mutex<T>本身就会触发安全问题:

  • Rc的引用计数修改依赖非原子操作,当你在另一个线程中通过MutexGuard操作Rc时,引用计数的增减无法保证线程安全,最终会导致内存泄漏、双重释放等内存错误。
  • Rust禁止Mutex<T>实现Send当T不满足Send,就是为了阻止这种跨线程传递的行为,从源头避免风险。

示例代码(Rust会编译报错,因为Rc不满足Send):

use std::sync::Mutex;
use std::rc::Rc;
use std::thread;

fn main() {
    let mutex = Mutex::new(Rc::new(10));
    // 编译错误:`Rc<u32>` cannot be sent between threads safely
    thread::spawn(move || {
        let mut guard = mutex.lock().unwrap();
        *Rc::get_mut(&mut guard).unwrap() += 1;
    }).join().unwrap();
}

2. 内部类型不满足Sync时的风险

Sync标记表示类型可以安全地被多个线程共享不可变引用。如果T不满足Sync(比如包含裸指针且未做同步保护的自定义类型),即使有Mutex保护,也可能出现绕过锁的内存访问:

  • 假设T内部持有一个裸指针,指向自身的某个字段,当你通过MutexGuard拿到T的引用后,其他线程可能通过这个裸指针直接访问内部数据,完全绕过Mutex的同步,导致数据竞争或悬垂指针等问题。
  • Rust要求T满足Sync才能让Mutex<T>成为Sync,确保即使Mutex<T>被多个线程共享,内部数据的访问也不会出现安全漏洞。

示例场景(自定义非Sync类型):

use std::sync::Mutex;
use std::ptr;
use std::thread;

// 自定义非Sync类型:内部持有裸指针,未做同步保护
struct UnsafeData {
    value: u32,
    ptr: *mut u32,
}

unsafe impl Send for UnsafeData {} // 手动实现Send,但不实现Sync

fn main() {
    let data = UnsafeData {
        value: 0,
        ptr: ptr::null_mut(),
    };
    let mutex = Mutex::new(data);
    // 编译错误:`UnsafeData` cannot be shared between threads safely
    let mutex_ref = &mutex;
    thread::spawn(move || {
        let mut guard = mutex_ref.lock().unwrap();
        guard.ptr = &mut guard.value;
    }).join().unwrap();
}

总结

锁只能保证锁保护范围内的线程安全,但内存安全的范畴更广——跨线程传递所有权、跨线程共享引用的安全性,需要由Send和Sync标记来约束。Rust通过这些标记,在编译期就阻止了那些即使加锁也可能出现的内存安全风险,而不是等到运行时才暴露问题。

内容的提问来源于stack exchange,提问作者QuantumDot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 05:45:20