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

Rust多线程中如何实现共享内存的可变与不可变访问

跨线程读写共享资源的Rust实现问题

我有一个多线程程序,需要跨线程访问资源:部分线程写入资源,部分线程读取资源。不确定这是否属于全局可变单例场景(我的设置并非全局,但解决方案可能类似)。

以下是简化后的代码示例,目标是让一个线程写入结构体,另一个线程读取同一结构体,确保写入后读取线程能获取到最新数据:

use std::{thread, time::Duration};

struct TagList {
  list: Vec<String>
}

impl TagList {
  fn add(self: &mut TagList, tag: String) {
    self.list.push(tag);
  }

  fn read(&self) -> Vec<String> {
    self.list.clone()
  }
}

fn main() {
    let mut list = TagList { list: vec![] };

    thread::spawn(move || {
       ["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
         list.add(tag.to_string());
         thread::sleep(Duration::from_millis(100));
       });
    });

    thread::spawn(move || {
      loop {
        dbg!(list.read());
        thread::sleep(Duration::from_millis(20));
      }
    });
}

这段代码触发了借用错误:

error[E0382]: use of moved value: `list`
  --> src/main.rs:79:19
   |
70 |     let mut list = TagList { list: vec![] };
   |         -------- move occurs because `list` has type `TagList`, which does not implement the `Copy` trait
71 | 
72 |     thread::spawn(move || {
   |                   ------- value moved into closure here
73 |        ["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
74 |          list.add(tag.to_string());
   |          ---- variable moved due to use in closure
...
79 |     thread::spawn(move || {
   |                   ^^^^^^^ value used here after move
80 |       dbg!(list.read());
   |            ---- use occurs due to use in closure

我尝试用Arc包裹列表解决问题:

use std::sync::Arc;
// ...

    let list = Arc::new(TagList { list: vec![] });
    let write_list = Arc::get_mut(&mut list).unwrap();
    let read_list = Arc::clone(&list);

    thread::spawn(move || {
       ["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
         write_list.add(tag.to_string());
         thread::sleep(Duration::from_millis(100));
       });
    });

    thread::spawn(move || {
      loop {
        dbg!(read_list.read());
        thread::sleep(Duration::from_millis(20));
      }
    });

但依然报错,显然我没理解Arc的工作原理和生命周期关系:

error[E0597]: `list` does not live long enough
  --> src/main.rs:71:35
   |
71 |     let write_list = Arc::get_mut(&mut list).unwrap();
   |                      -------------^^^^^^^^^-
   |                      |            |
   |                      |            borrowed value does not live long enough
   |                      argument requires that `list` is borrowed for `'static`
...
85 | }
   | - `list` dropped here while still borrowed

error[E0502]: cannot borrow `list` as immutable because it is also borrowed as mutable
  --> src/main.rs:72:32
   |
71 |     let write_list = Arc::get_mut(&mut list).unwrap();
   |                      -----------------------
   |                      |            |
   |                      |            mutable borrow occurs here
   |                      argument requires that `list` is borrowed for `'static`
72 |     let read_list = Arc::clone(&list);
   |                                ^^^^^ immutable borrow occurs here

我的疑问:

  • 这个功能是否可行?(我知道std::sync::mpsc能跨线程推送读取消息,类似功能应该存在)
  • 应该使用什么工具?Arc是合适的选择吗,还是找错了方向?
  • 需要学习哪些内容才能理解Rust中这类问题的典型解决方法?

解决方案与解释

功能完全可行

这类需求是典型的多线程共享可变资源场景,Rust标准库提供了成熟的工具实现,和mpsc属于不同的线程通信模式(mpsc是消息传递,这里是共享内存)。

为什么之前的Arc用法错误

Arc的作用是提供线程安全的引用计数,让多个线程能持有同一个资源的所有权,但它本身不提供内部可变性,也不允许同时可变访问。Arc::get_mut获取可变引用的前提是当前只有一个Arc实例存在,一旦克隆Arc,这个方法就会返回None;而且你拿到的可变引用绑定到原Arc的生命周期上,无法安全传递到线程中(线程要求捕获的变量生命周期为'static)。

正确的工具组合:Arc + 同步原语

要实现多线程下的读写,需要结合Arc和以下同步原语之一:

1. Arc<Mutex<TagList>>:互斥锁

适用于读写频率相近或写操作频繁的场景,同一时间只能有一个线程获取锁(无论读还是写)。

示例代码:

use std::{thread, time::Duration, sync::{Arc, Mutex}};

struct TagList {
  list: Vec<String>
}

impl TagList {
  fn add(&mut self, tag: String) {
    self.list.push(tag);
  }

  fn read(&self) -> Vec<String> {
    self.list.clone()
  }
}

fn main() {
    let list = Arc::new(Mutex::new(TagList { list: vec![] }));

    // 克隆Arc给写线程
    let write_list = Arc::clone(&list);
    thread::spawn(move || {
       ["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
         // 获取锁,unwrap忽略锁被poisoned的情况(生产环境需处理)
         let mut guard = write_list.lock().unwrap();
         guard.add(tag.to_string());
         // 锁在guard离开作用域时自动释放
         thread::sleep(Duration::from_millis(100));
       });
    });

    // 克隆Arc给读线程
    let read_list = Arc::clone(&list);
    thread::spawn(move || {
      loop {
        let guard = read_list.lock().unwrap();
        dbg!(guard.read());
        thread::sleep(Duration::from_millis(20));
      }
    });

    // 主线程等待子线程完成,否则主线程退出会直接终止所有子线程
    thread::sleep(Duration::from_millis(500));
}

2. Arc<RwLock<TagList>>:读写锁

适用于读多写少的场景,允许多个读线程同时获取读锁,同一时间只能有一个写线程获取写锁,写锁会阻塞所有读锁和其他写锁。

示例代码:

use std::{thread, time::Duration, sync::{Arc, RwLock}};

struct TagList {
  list: Vec<String>
}

impl TagList {
  fn add(&mut self, tag: String) {
    self.list.push(tag);
  }

  fn read(&self) -> Vec<String> {
    self.list.clone()
  }
}

fn main() {
    let list = Arc::new(RwLock::new(TagList { list: vec![] }));

    let write_list = Arc::clone(&list);
    thread::spawn(move || {
       ["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
         // 获取写锁
         let mut guard = write_list.write().unwrap();
         guard.add(tag.to_string());
         thread::sleep(Duration::from_millis(100));
       });
    });

    let read_list = Arc::clone(&list);
    thread::spawn(move || {
      loop {
        // 获取读锁
        let guard = read_list.read().unwrap();
        dbg!(guard.read());
        thread::sleep(Duration::from_millis(20));
      }
    });

    thread::sleep(Duration::from_millis(500));
}

需要学习的核心内容

  1. Rust的所有权与借用规则:这是理解线程安全问题的基础,初始错误就是所有权转移导致的。
  2. 线程安全与Send/Sync trait:Rust通过这两个trait标记类型是否能安全跨线程传递或共享,Arc、Mutex、RwLock都实现了这两个trait。
  3. 共享内存的同步原语:除了Mutex和RwLock,还可了解Condvar(条件变量)等工具,用于更复杂的线程同步场景。
  4. 内部可变性:RefCell是单线程的内部可变性工具,Mutex/RwLock是线程安全的内部可变性工具,结合Arc就能实现多线程共享可变资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 03:35:20