범위가 지정된 스레드
스레드 간에 빌리기
범위가 지정된 스레드은(는) CoddyKit의 무료 Learn Rust Coding 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Learn Rust Coding 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Learn Rust Coding 강의에는 총 4개의 강의가 포함되어 있습니다.
스레드에서 빌리기의 문제
일반적인 thread::spawn 클로저는 'static이어야 합니다. 스레드가 해당 변수를 소유한 함수보다 오래 실행될 수 있으므로 지역 변수를 빌릴 수 없습니다. 그래서 move와 Arc를 자주 사용합니다.
범위 지정 스레드는 범위가 끝나기 전에 모든 스레드가 완료되도록 보장하여 이 문제를 해결합니다. 따라서 지역 데이터를 안전하게 빌릴 수 있습니다.
spawn에 'static이 필요한 이유
thread::spawn을 사용하면 생성된 스레드가 main이나 다른 함수가 반환된 뒤에도 계속 실행될 수 있습니다. 이 스레드가 지역 변수를 빌렸다면 스레드가 계속 사용하는 동안 해당 변수가 삭제될 수 있습니다. Rust는 컴파일 시점에 이를 금지하므로 소유한 데이터를 클로저 안으로 이동해야 합니다.
use std::thread;
fn main() {
let nums = vec![1, 2, 3];
// move transfers ownership into the thread
let handle = thread::spawn(move || {
println!("in thread: {:?}", nums);
});
handle.join().unwrap();
}thread::scope 소개
Rust 1.63에서 안정화된 std::thread::scope는 스레드가 지역 변수를 빌릴 수 있는 범위를 만듭니다. 범위는 내부의 모든 스레드가 완료될 때까지 대기하므로 빌린 참조가 더 이상 유효하지 않게 되는 일은 없습니다.
범위 핸들 s를 사용하여 thread::spawn 대신 s.spawn(...)으로 스레드를 생성합니다.
use std::thread;
fn main() {
let data = vec![10, 20, 30];
thread::scope(|s| {
s.spawn(|| {
println!("borrowed: {:?}", data);
});
});
// data is still usable here
println!("after scope: {:?}", data);
}move 없이 빌리기
thread::scope 내부에서는 move 없이 참조를 통해 지역 변수를 읽을 수 있습니다. 일반 참조와 마찬가지로 여러 범위 지정 스레드가 같은 데이터에 대한 불변 빌림을 동시에 공유할 수 있습니다.
use std::thread;
fn main() {
let message = String::from("shared text");
thread::scope(|s| {
s.spawn(|| println!("thread 1 sees: {}", message));
s.spawn(|| println!("thread 2 sees: {}", message));
});
println!("main still owns: {}", message);
}슬라이스로 작업 나누기
일반적인 패턴은 슬라이스를 나누고 각 스레드가 한 부분을 처리하도록 하는 것입니다. 범위 지정 스레드를 사용하면 각 스레드가 원본 슬라이스의 일부를 직접 빌릴 수 있으므로 복제할 필요 없이 깔끔하게 구현할 수 있습니다.
use std::thread;
fn main() {
let numbers = [1, 2, 3, 4, 5, 6];
let (left, right) = numbers.split_at(3);
thread::scope(|s| {
s.spawn(|| {
let sum: i32 = left.iter().sum();
println!("left sum: {}", sum);
});
s.spawn(|| {
let sum: i32 = right.iter().sum();
println!("right sum: {}", sum);
});
});
}반환 값 수집하기
일반 스레드와 마찬가지로 s.spawn은 ScopedJoinHandle을 반환합니다. .join()을 호출하면 스레드의 반환 값을 얻습니다. 핸들을 수집한 뒤 스레드를 생성하고, 나중에 결합하여 결과를 모을 수 있습니다.
use std::thread;
fn main() {
let inputs = [2, 4, 6];
let mut handles = vec![];
thread::scope(|s| {
for &x in &inputs {
handles.push(s.spawn(move || x * x));
}
let results: Vec<i32> = handles.into_iter()
.map(|h| h.join().unwrap())
.collect();
println!("{:?}", results);
});
}가변 빌림에는 주의가 필요합니다
두 범위 지정 스레드가 같은 데이터를 동시에 가변으로 빌릴 수는 없습니다. 그렇게 하면 Rust의 별칭 규칙을 위반하게 됩니다. 여러 스레드에서 공유 데이터를 변경하려면 여전히 Mutex가 필요하지만, 하나의 스레드는 서로 겹치지 않는 부분을 고유하게 가변으로 빌릴 수 있습니다.
아래에서는 각 스레드가 split_at_mut을 사용해 배열의 서로 다른 절반을 변경합니다.
use std::thread;
fn main() {
let mut data = [1, 2, 3, 4];
let (a, b) = data.split_at_mut(2);
thread::scope(|s| {
s.spawn(|| { for x in a.iter_mut() { *x *= 10; } });
s.spawn(|| { for x in b.iter_mut() { *x += 100; } });
});
println!("{:?}", data);
}범위가 자동으로 스레드를 결합합니다
범위 지정 스레드마다 join을 호출할 필요는 없습니다. scope 클로저가 반환되면 Rust는 아직 결합되지 않은 모든 스레드를 자동으로 결합한 후 계속 진행합니다. 따라서 전체 스레드 수명 동안 빌림이 유효하다고 보장됩니다.
use std::thread;
use std::time::Duration;
fn main() {
let label = String::from("task");
thread::scope(|s| {
s.spawn(|| {
thread::sleep(Duration::from_millis(30));
println!("{} done", label);
});
println!("spawned, scope will wait");
});
println!("all scoped threads finished");
}범위와 공유 변경 결합하기
스레드가 같은 값을 변경해야 한다면 범위 지정 스레드와 Mutex를 함께 사용하세요. 범위가 스레드에서 지역 Mutex를 직접 빌릴 수 있게 하므로 Arc는 생략할 수 있습니다.
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
thread::scope(|s| {
for _ in 0..5 {
s.spawn(|| {
let mut n = counter.lock().unwrap();
*n += 1;
});
}
});
println!("counter = {}", *counter.lock().unwrap());
}범위 지정 스레드와 생성 스레드: 언제 무엇을 사용할까요
작업 범위가 정해져 있고 하나의 함수 안에서 완료되며, Arc나 복제 없이 스택 데이터를 빌리고 싶다면 범위 지정 스레드를 사용하세요.
스레드가 현재 함수보다 오래 실행되거나 프로그램 전체 수명 동안 실행되어야 한다면 thread::spawn을 사용하세요. 범위 지정 스레드는 자신의 범위를 벗어날 수 없습니다.
병렬 map 예제
지금까지 배운 내용을 하나로 합쳐 보겠습니다. 입력을 빌리면서 벡터의 각 요소를 자체 스레드에서 변환하고, 결과를 순서대로 수집하는 간단한 병렬 map입니다.
use std::thread;
fn parallel_double(items: &[i32]) -> Vec<i32> {
let mut handles = Vec::new();
let mut out = Vec::new();
thread::scope(|s| {
for &x in items {
handles.push(s.spawn(move || x * 2));
}
for h in handles {
out.push(h.join().unwrap());
}
});
out
}
fn main() {
let nums = vec![1, 2, 3, 4];
println!("{:?}", parallel_double(&nums));
}빠른 확인
범위 지정 스레드에 대한 이해도를 확인해 보세요.
복습
범위 지정 스레드에 대해 배웠습니다.
thread::spawn에는'static클로저가 필요하지만 범위 지정 스레드에는 필요하지 않습니다.thread::scope를 사용하면 스레드가 지역 변수를 안전하게 빌릴 수 있습니다.- 범위는 반환되기 전에 모든 스레드를 자동으로 결합합니다.
- 가변 공유에는 여전히
Mutex가 필요하지만 범위 안에서는Arc가 필요하지 않습니다. - 범위가 정해진 함수 내부의 병렬 작업에는 범위 지정 스레드를 사용하세요.
자주 묻는 질문
“범위가 지정된 스레드” 강의는 무료인가요?
네 — “범위가 지정된 스레드” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Learn Rust Coding 강의 전체를 잠금 해제할 수 있습니다. Learn Rust Coding 강의에는 총 4개의 강의가 포함되어 있습니다.
“범위가 지정된 스레드”에서 뭘 배우나요?
스레드 간에 빌리기 브라우저에서 직접 실행하는 실습 코드로 Learn Rust Coding을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Learn Rust Coding을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Learn Rust Coding은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“범위가 지정된 스레드” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Learn Rust Coding 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Learn Rust Coding 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- mpsc 채널
- Arc/Mutex로 상태 공유하기
- 범위가 지정된 스레드
- Crossbeam 채널