スレッドによる恐れのない並行処理
データ競合のない並行処理を実現する、スレッドやメッセージパッシングなどRustの基本機能について詳しく学びます。
「スレッドによる恐れのない並行処理」はCoddyKit上の無料Learn Rust Codingレッスンです。 これはレッスン3/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応の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はMultiple Producer, Single Consumerの略です。これは、多数のスレッドがメッセージを送信できる一方、受信できるスレッドは1つだけであることを意味します。
チャネルを作成するには、mpsc::channel()を呼び出します。次の要素を含むタプルが返されます。
- Sender(
tx):メッセージの送信に使用します。 - Receiver(
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」はMultiple Producersを意味します。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の並行性プリミティブについて、次の文のうち正しいものはどれですか?
振り返り:スレッドとメッセージパッシング
よくできました!Rustにおける恐れ知らずの並行性の基礎を学びました。
std::thread::spawnを使って新しいスレッドを作成します。JoinHandle::join()を使ってスレッドの完了を待機します。moveキーワードによって、データの所有権を安全にスレッドへ移動します。std::sync::mpscは、スレッド間通信のための堅牢なメッセージパッシングシステムを提供します。- Rustの所有権規則はコンパイル時にデータ競合を防ぐため、並行プログラミングをより安全にします。
このレッスンは、応答性と効率に優れたアプリケーションを構築するための確かな基礎となります。練習を続けてください。
よくある質問
「スレッドによる恐れのない並行処理」レッスンは無料ですか?
はい。「スレッドによる恐れのない並行処理」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Learn Rust Codingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Learn Rust Codingコースには全3レッスンが含まれています。
「スレッドによる恐れのない並行処理」で何を学びますか?
データ競合のない並行処理を実現する、スレッドやメッセージパッシングなどRustの基本機能について詳しく学びます。 ブラウザで直接実行するハンズオンコードでLearn Rust Codingを演習し、24時間対応の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
- スレッドによる恐れのない並行処理