0Pricing
Learn Rust Coding · 课时

使用线程实现无畏并发

深入了解 Rust 的并发编程原语,包括线程和消息传递,并确保不会发生数据竞争。

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

并发简介

欢迎学习使用线程实现无畏并发!在本课中,您将学习如何让 Rust 程序同时执行多项任务。

并发是指让多个计算过程看起来像是在同一时间执行。它对于构建响应迅速的应用程序以及充分利用现代多核处理器至关重要。

  • 线程是程序中的轻量级执行单元。
  • 每个线程都可以运行代码中的一个独立部分。
  • Rust 的所有权系统有助于防止常见的并发错误。

创建您的第一个线程

Rust 提供了 std::thread::spawn 来创建新线程。您需要向它传入一个闭包(匿名函数),其中包含新线程应运行的代码。

尝试运行此示例,看看线程的实际运行效果:

use std::thread;
use std::time::Duration;

fn main() {
  println!("Hello from the main thread!");

  thread::spawn(|| {
    for i in 1..=5 {
      println!("Hi number {} from the spawned thread!", i);
      thread::sleep(Duration::from_millis(1));
    }
  });

  for i in 1..=3 {
    println!("Hi number {} from the main thread!", i);
    thread::sleep(Duration::from_millis(1));
  }
}

使用 `join` 等待线程

在上一个示例中,生成的线程可能会在主线程退出之前尚未完成。这是因为主线程默认不会等待生成的线程。

为了确保生成的线程完成工作,我们可以对其 JoinHandle 使用 join() 方法。该方法会阻塞当前线程,直到被等待的线程完成。

use std::thread;
use std::time::Duration;

fn main() {
  let handle = thread::spawn(|| {
    for i in 1..=5 {
      println!("Thread: {}", i);
      thread::sleep(Duration::from_millis(1));
    }
  });

  for i in 1..=3 {
    println!("Main: {}", i);
    thread::sleep(Duration::from_millis(1));
  }

  handle.join().unwrap(); // Wait for the spawned thread to finish
  println!("Spawned thread has finished!");
}

将数据移入线程

当您在 spawn 闭包中使用环境中的变量时,Rust 的所有权规则就会发挥作用。默认情况下,闭包会尝试借用变量。

如果生成的线程的生命周期超过了定义该变量的主线程作用域,就可能导致悬垂引用。要解决此问题,请在闭包参数前使用 move 关键字。

  • move 会强制闭包获取其捕获变量的所有权。
  • 这样可以确保数据在新线程的整个生命周期内都有效。
use std::thread;

fn main() {
  let data = String::from("Hello from outer scope");

  let handle = thread::spawn(move || { // Use 'move' to take ownership of 'data'
    println!("Data in thread: {}", data);
  });

  handle.join().unwrap();
  // println!("Data after thread: {}", data); // This would cause a compile error!
  println!("Main thread finished.");
}

使用消息传递进行通信

虽然 Rust 支持在线程之间直接共享数据(共享状态),但这需要进行谨慎的同步(例如使用 Mutex 和 Arc;相关内容将在其他课程中介绍)。

对于并发而言,消息传递是一种更安全且通常更简单的方法。线程通过相互发送消息进行通信,从而避免直接访问共享内存。

  • 一个线程发送数据。
  • 另一个线程接收数据。
  • Rust 标准库为此提供了 std::sync::mpsc。

设置 `mpsc` 通道

std::sync::mpsc 代表多生产者、单消费者。这意味着多个线程可以发送消息,但只能有一个线程接收消息。

要创建通道,请调用 mpsc::channel()。它会返回一个包含以下内容的元组:

  • 发送端(tx):用于发送消息。
  • 接收端(rx):用于接收消息。

让我们看看如何创建通道并发送一条简单的消息。

use std::sync::mpsc;
use std::thread;

fn main() {
  // Create a new channel
  let (tx, rx) = mpsc::channel();

  thread::spawn(move || {
    let val = String::from("hi");
    tx.send(val).unwrap(); // Send the message
    // println!("val is {}", val); // Error: val moved to tx.send()
  });

  // Receive the message in the main thread
  let received = rx.recv().unwrap();
  println!("Got: {}", received);
}

发送多条消息

您可以通过同一个通道发送多条消息。Receiver 提供了用于处理这些消息的方法:

  • rx.recv():阻塞当前线程,直到收到消息。返回一个 Result。
  • rx.try_recv():非阻塞。会立即返回一个 Result,其中要么包含消息,要么在没有可用消息时包含错误。
  • 您也可以直接遍历 Receiver,持续获取消息,直到发送端关闭。
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
  let (tx, rx) = mpsc::channel();

  thread::spawn(move || {
    let msgs = vec!["hi", "from", "the", "thread"];
    for msg in msgs {
      tx.send(String::from(msg)).unwrap();
      thread::sleep(Duration::from_millis(10));
    }
  });

  // Iterate over the receiver to get all messages
  for received in rx {
    println!("Got: {}", received);
  }
  println!("All messages received!");
}

多生产者、单消费者

mpsc 中的“MP”代表多生产者。您可以 clone 一个 Sender,让多个线程向同一个 Receiver 发送消息。

每个克隆出的 Sender 都共享对同一通道的访问,因此不同线程可以并发发送消息。

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
  let (tx, rx) = mpsc::channel();

  let tx1 = tx.clone(); // Clone the sender for another thread
  thread::spawn(move || {
    tx1.send(String::from("hello from tx1")).unwrap();
  });

  thread::spawn(move || {
    tx.send(String::from("hello from tx2")).unwrap();
  });

  // Collect all messages
  let mut received_messages: Vec<String> = vec![];
  for received in rx {
    received_messages.push(received);
    if received_messages.len() == 2 { break; } // Assuming 2 messages for this example
  }
  println!("Received: {:?}", received_messages);
}

使用 Rust 实现无畏并发

Rust 的所有权系统和类型系统对于实现无畏并发至关重要。通过在编译时强制执行规则,Rust 可以防止数据竞争等常见并发错误。

  • 所有权可以防止同一数据存在多个可变引用。
  • move 关键字可以确保数据安全地转移到新线程。
  • 消息传递(mpsc)完全避免共享内存,从设计上确保通信安全。

这些机制让您可以放心编写并发代码,因为编译器会捕获许多潜在问题。

并发概念检查

以下关于 Rust 并发原语的说法哪些是 TRUE?

回顾:线程与消息传递

做得很好!您已经学习了 Rust 无畏并发的基础知识:

  • 我们使用 std::thread::spawn 创建新线程。
  • 我们使用 JoinHandle::join() 等待线程完成。
  • move 关键字可以将数据的所有权安全地转移到线程中。
  • std::sync::mpsc 提供了健壮的消息传递系统,用于线程间通信。
  • Rust 的所有权规则会在编译时防止数据竞争,使并发编程更加安全。

本课为构建响应迅速且高效的应用程序打下了坚实基础。请继续练习!

常见问题解答

「使用线程实现无畏并发」课时是免费的吗?

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

「使用线程实现无畏并发」这节课中我会学到什么?

深入了解 Rust 的并发编程原语,包括线程和消息传递,并确保不会发生数据竞争。 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

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

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

「使用线程实现无畏并发」课时需要多长时间?

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

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

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

此课程中的所有课时

  1. Box、Rc 与 Arc 智能指针
  2. 内部可变性:RefCell、Cell
  3. 使用线程实现无畏并发
← 返回 Learn Rust Coding