0Pricing
Learn Rust Coding · 课时

使用 Arc 和 Mutex 共享数据

安全地修改共享状态。

使用 Arc 和 Mutex 共享数据 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。

共享状态的必要性

有时,多个线程必须读取或更新同一份数据。将所有权移动给单个线程是不够的。

Rust 通过 Arc 提供跨线程的安全共享所有权,并通过 Mutex 提供安全的可变访问。二者结合后,就能实现共享的可变状态。

Arc:原子引用计数

Arc 表示原子引用计数。它类似于 Rc,但其计数器使用原子操作,因此在线程之间使用也是安全的。

克隆 Arc 不会复制数据,只会增加计数,并返回一个指向同一值的另一个句柄。

use std::sync::Arc;

fn main() {
    let shared = Arc::new(vec![1, 2, 3]);
    let clone = Arc::clone(&shared);
    println!("{:?} {:?}", shared, clone);
}

在线程之间共享 Arc

要共享只读数据,请为每个线程克隆一次 Arc,并将每个克隆值移动到对应线程中。所有线程都指向同一份分配的数据。

由于 Arc 实现了 Send 和 Sync,因此代码可以顺利编译。

use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![10, 20, 30]);
    let mut handles = vec![];
    for i in 0..3 {
        let d = Arc::clone(&data);
        handles.push(thread::spawn(move || println!("{}", d[i])));
    }
    for h in handles { h.join().unwrap(); }
}

Arc 本身只能提供只读访问

Arc 提供共享所有权,但对内部值只能提供共享的不可变访问。

您不能直接通过 Arc 修改数据,因为多个线程同时持有它时会发生竞争。您需要借助锁来实现内部可变性。

Mutex:互斥

Mutex 保护数据,使其在同一时间只能被一个线程访问。您可以调用 lock 来获取访问权限。

lock 返回一个 Result;解包后会得到一个智能指针守卫。其他线程会阻塞,直到该守卫被丢弃。

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(0);
    {
        let mut guard = m.lock().unwrap();
        *guard += 5;
    }
    println!("{:?}", m);
}

守卫与 RAII

lock 返回的值是一个 MutexGuard。您可以通过使用 * 解引用它来访问内部数据。

当守卫离开作用域时,锁会自动释放。这种 RAII 风格可以避免忘记解锁。

组合使用 Arc 和 Mutex

要在线程之间共享可变状态,请将 Mutex 包装在 Arc 中。Arc 负责共享所有权;Mutex 负责保护修改操作。

Arc<Mutex<T>> 这种模式是 Rust 中实现共享可变状态的标准方式。

use std::sync::{Arc, Mutex};

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let c = Arc::clone(&counter);
    *c.lock().unwrap() += 1;
    println!("{}", *counter.lock().unwrap());
}

共享计数器

这里有十个线程,每个线程都会将共享计数器加一。每个线程都会克隆 Arc、锁定 Mutex,然后加一。

由于锁会将访问操作串行化,最终总数始终恰好为十。

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

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..10 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            *c.lock().unwrap() += 1;
        }));
    }
    for h in handles { h.join().unwrap(); }
    println!("{}", *counter.lock().unwrap());
}

缩短临界区

持有锁期间执行的代码称为临界区。其他线程会在那里等待,因此临界区应尽可能短。

先加锁,执行最少的更新操作,然后释放锁。持有锁时应避免进行大量计算或输入输出操作。

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(Vec::new());
    {
        let mut v = m.lock().unwrap();
        v.push(1);
    } // lock released here
    println!("{:?}", m.lock().unwrap());
}

死锁与中毒

如果一个线程两次锁定同一个 Mutex,或者两个线程以相反的顺序锁定两个互斥锁,就可能发生死锁并永久挂起。

如果线程在持有锁时发生 panic,该 Mutex 就会中毒,之后调用 lock 会返回 Err。

使用 RwLock 支持多个读取者

当读取次数远多于写入次数时,RwLock 可能比 Mutex 更合适。它允许多个读取者并发访问,或允许一个写入者独占访问。

使用 read 进行共享访问,使用 write 进行独占访问。

use std::sync::RwLock;

fn main() {
    let lock = RwLock::new(5);
    {
        let r = lock.read().unwrap();
        println!("read {}", *r);
    }
    *lock.write().unwrap() += 1;
    println!("{}", *lock.read().unwrap());
}

快速检查

检验您对 Arc 和 Mutex 的理解。

回顾

您已经了解到,Arc 通过原子引用计数在线程之间共享所有权,但只能提供不可变访问。

Mutex 保护修改操作,并提供一个在被丢弃时释放锁的守卫。将二者组合为 Arc<Mutex<T>>,即可实现共享可变状态,同时要注意死锁和中毒问题。

接下来,您将 join 线程并收集它们的结果。

常见问题解答

「使用 Arc 和 Mutex 共享数据」课时是免费的吗?

是的 — 「使用 Arc 和 Mutex 共享数据」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。

「使用 Arc 和 Mutex 共享数据」这节课中我会学到什么?

安全地修改共享状态。 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Learn Rust Coding 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「使用 Arc 和 Mutex 共享数据」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Learn Rust Coding 课中编写并运行代码吗?

能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 创建线程
  2. 将数据移入线程
  3. 使用 Arc 和 Mutex 共享数据
  4. 加入线程并收集结果
← 返回 Learn Rust Coding