0Pricing
Learn Rust Coding · Урок

Бесстрашная многопоточность

Изучите примитивы Rust для параллельного программирования, включая потоки и передачу сообщений, с гарантией отсутствия состязаний за данные.

«Бесстрашная многопоточность» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 3 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 3 уроков всего.

Части этого урока еще не переведены и отображаются на английском.

Intro to Concurrency

Welcome to Fearless Concurrency with Threads! In this lesson, we'll learn how to make your Rust programs do multiple things at once.

Concurrency means executing multiple computations seemingly at the same time. It's vital for responsive applications and utilizing modern multi-core processors.

  • Threads are lightweight units of execution within a program.
  • Each thread can run a separate part of your code.
  • Rust's ownership system helps prevent common concurrency bugs.

Creating Your First Thread

Rust provides std::thread::spawn to create new threads. You pass it a closure (an anonymous function) that contains the code the new thread should run.

Try running this example to see threads in action:

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));
  }
}

Waiting for Threads with `join`

In the last example, the spawned thread might not finish before the main thread exits. This is because the main thread doesn't wait for spawned threads by default.

To ensure a spawned thread completes its work, we use the join() method on its JoinHandle. This blocks the current thread until the joined thread finishes.

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!");
}

Moving Data into Threads

When you use variables from the environment inside a spawn closure, Rust's ownership rules come into play. By default, closures try to borrow variables.

If the spawned thread outlives the main thread's scope where the variable was defined, this can lead to a dangling reference. To fix this, use the move keyword before the closure's parameters.

  • move forces the closure to take ownership of its captured variables.
  • This ensures the data is valid for the entire lifetime of the new thread.
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.");
}

Communicating with Message Passing

While sharing data directly between threads (shared state) is possible in Rust, it requires careful synchronization (e.g., with Mutex and Arc, covered in other lessons).

A safer and often simpler approach for concurrency is message passing. Threads communicate by sending messages to each other, avoiding direct shared memory access.

  • One thread sends data.
  • Another thread receives data.
  • Rust's standard library provides std::sync::mpsc for this.

Setting Up an `mpsc` Channel

std::sync::mpsc stands for Multiple Producer, Single Consumer. This means many threads can send messages, but only one thread can receive them.

To create a channel, you call mpsc::channel(). It returns a tuple containing:

  • A Sender (tx): Used to send messages.
  • A Receiver (rx): Used to receive messages.

Let's see how to create one and send a simple message.

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);
}

Sending Multiple Messages

You can send multiple messages through the same channel. The Receiver has methods to handle this:

  • rx.recv(): Blocks the current thread until a message is received. Returns a Result.
  • rx.try_recv(): Non-blocking. Returns a Result immediately, either with a message or an error if no message is available.
  • You can also iterate directly over the Receiver to get messages until the sender closes.
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!");
}

Multiple Producers, Single Consumer

The 'MP' in mpsc means Multiple Producers. You can clone a Sender to have multiple threads send messages to the same Receiver.

Each cloned Sender shares access to the same channel, allowing concurrent message sending from different threads.

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);
}

Fearless Concurrency with Rust

Rust's ownership and type system are crucial for achieving fearless concurrency. By enforcing rules at compile time, Rust prevents common concurrency bugs like data races.

  • Ownership prevents multiple mutable references to the same data.
  • move keyword ensures data is safely transferred to a new thread.
  • Message passing (mpsc) avoids shared memory entirely, making communication safe by design.

These mechanisms allow you to write concurrent code with confidence, knowing the compiler will catch many potential issues.

Concurrency Concepts Check

Which of the following statements about Rust's concurrency primitives are TRUE?

Recap: Threads & Message Passing

Great job! You've learned the fundamentals of fearless concurrency in Rust:

  • We create new threads using std::thread::spawn.
  • We use JoinHandle::join() to wait for a thread to complete.
  • The move keyword safely transfers ownership of data into a thread.
  • std::sync::mpsc provides a robust message passing system for inter-thread communication.
  • Rust's ownership rules prevent data races at compile time, making concurrent programming safer.

This lesson provides a solid foundation for building responsive and efficient applications. Keep practicing!

Часто задаваемые вопросы

Урок «Бесстрашная многопоточность» бесплатный?

Да — полный текст урока «Бесстрашная многопоточность» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 3 уроков всего.

Чему я научусь в уроке «Бесстрашная многопоточность»?

Изучите примитивы Rust для параллельного программирования, включая потоки и передачу сообщений, с гарантией отсутствия состязаний за данные. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Learn Rust Coding?

Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 3.

Сколько времени занимает урок «Бесстрашная многопоточность»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Learn Rust Coding?

Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Умные указатели Box, Rc и Arc
  2. Внутренняя изменяемость: RefCell, Cell
  3. Бесстрашная многопоточность
← Назад к Learn Rust Coding