스레드로 두려움 없는 동시성 구현하기
스레드와 메시지 전달을 포함한 Rust의 동시 프로그래밍 기본 요소를 살펴보고 데이터 경합이 발생하지 않도록 합니다.
스레드로 두려움 없는 동시성 구현하기은(는) CoddyKit의 무료 Learn Rust Coding 강의입니다. 이것은 3개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 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.
moveforces 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::mpscfor 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 aResult.rx.try_recv(): Non-blocking. Returns aResultimmediately, either with a message or an error if no message is available.- You can also iterate directly over the
Receiverto 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.
movekeyword 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
movekeyword safely transfers ownership of data into a thread. std::sync::mpscprovides 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 AI 튜터), CoddyKit PRO로 업그레이드하면 Learn Rust Coding 강의 전체를 잠금 해제할 수 있습니다. Learn Rust Coding 강의에는 총 3개의 강의가 포함되어 있습니다.
“스레드로 두려움 없는 동시성 구현하기”에서 뭘 배우나요?
스레드와 메시지 전달을 포함한 Rust의 동시 프로그래밍 기본 요소를 살펴보고 데이터 경합이 발생하지 않도록 합니다. 브라우저에서 직접 실행하는 실습 코드로 Learn Rust Coding을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Learn Rust Coding을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Learn Rust Coding은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 3개 중 3번째 강의입니다.
“스레드로 두려움 없는 동시성 구현하기” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Learn Rust Coding 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Learn Rust Coding 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- Box, Rc, Arc 스마트 포인터
- 내부 변경 가능성: RefCell, Cell
- 스레드로 두려움 없는 동시성 구현하기