並行処理や非同期処理は、アプリケーションが同時に複数のタスクを処理するための方法で、Rustでは効率的かつ安全に実行できる設計がされています。Rustは所有権モデルにより、メモリ管理やスレッドセーフティをコンパイル時に保証するため、並行処理や非同期処理においても他の言語と比べてバグが少なくなりやすい特徴があります。
この章では、Rustにおけるスレッド管理と非同期処理について詳しく解説します。具体的には、スレッドの基本的な使い方から、async/awaitによる非同期プログラミングの書き方までを網羅的に学びます。
Rustでは、標準ライブラリが提供するstd::thread
モジュールを使ってスレッドを生成し、並行処理を行えます。スレッドはオペレーティングシステムの中で軽量な実行単位として扱われ、複数のスレッドを作成することで、プログラムは複数のタスクを並行して処理できます。
まず、Rustでスレッドを作成する基本的な方法を見ていきましょう。スレッドの作成には、thread::spawn
関数を使用します。この関数は、新しいスレッドで実行したいクロージャ(無名関数)を受け取ります。
use std::thread;
use std::time::Duration;
fn main() {
// 新しいスレッドを生成
let handle = thread::spawn(|| {
for i in 1..10 {
println!("新しいスレッド: {}", i);
thread::sleep(Duration::from_millis(1));
}
});
// メインスレッドでの処理
for i in 1..5 {
println!("メインスレッド: {}", i);
thread::sleep(Duration::from_millis(1));
}
// 新しいスレッドの終了を待つ
handle.join().unwrap();
}
上記の例では、thread::spawn
で新しいスレッドを作成し、その中でループを実行しています。また、メインスレッドでは異なるループが実行され、並行して動作します。join
メソッドを使って、新しいスレッドが終了するまでメインスレッドが待機するようにしています。
スレッド間でデータを共有する場合、データ競合が発生しないように注意が必要です。Rustでは、スレッド間の安全なデータ共有を行うためにMutex
(ミューテックス)やArc
(アトミック参照カウント型)を使用します。
以下は、複数のスレッドでカウンタを共有する例です。
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("結果: {}", *counter.lock().unwrap());
}
ここでは、Arc
とMutex
を組み合わせて使用することで、複数のスレッドから安全にカウンタにアクセスできるようにしています。Arc
はスレッド間でデータを共有するためのスマートポインタで、Mutex
は排他制御を行い、データへのアクセスが他のスレッドと競合しないようにします。
async
/await
スレッドとは別に、Rustでは非同期処理を実現するためにasync
/await
構文が導入されています。非同期処理は、特定のタスクが終了するのを待たずに他のタスクを進めることができるため、リソースを効率的に活用できます。例えば、I/O待機中に他の処理を行うことで、スループットの向上が図れます。
Rustで非同期処理を行うには、関数やブロックにasync
キーワードを付けます。以下は、非同期関数の基本的な定義方法です。
async fn say_hello() {
println!("Hello, world!");
}
このsay_hello
関数は非同期関数であり、呼び出し元に制御を戻すことなく即座に完了します。しかし、この関数を実行するには、「実行器(Executor)」と呼ばれるランタイムが必要です。
非同期関数を実行するためには、await
を使用してその結果を待つか、実行器で関数を実行します。以下のコードは、async
/await
の基本的な使用方法を示しています。
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
let task1 = async {
for i in 1..5 {
println!("タスク 1 - カウント: {}", i);
sleep(Duration::from_millis(500)).await;
}
};
let task2 = async {
for i in 1..5 {
println!("タスク 2 - カウント: {}", i);
sleep(Duration::from_millis(500)).await;
}
};
tokio::join!(task1, task2);
}
この例では、tokio::join!
マクロを使って、task1
とtask2
を同時に実行しています。sleep
関数を使用して500ミリ秒の遅延を導入しており、それぞれのタスクが並行して進行することがわかります。
以下は、非同期処理を用いて複数のHTTPリクエストを並行して実行する例です。ここでは、reqwest
クレートを使用して、複数のURLに非同期でアクセスしています。
use reqwest::Error;
use tokio::task;
async fn fetch_url(url: &str) -> Result<String, Error> {
let response = reqwest::get(url).await?;
let body = response.text().await?;
Ok(body)
}
#[tokio::main]
async fn main() {
let urls = vec![
"https://example.com",
"https://example.org",
"https://example.net",
];
let fetches = urls.into_iter().map(|url| {
task::spawn(async move {
match fetch_url(url).await {
Ok(content) => println!("Fetched content from {}: {}", url, &content[0..50]),
Err(e) => println!("Error fetching {}: {}", url, e),
}
})
});
for fetch in fetches {
fetch.await.unwrap();
}
}
ここでは、task::spawn
を使用して、各URLに非同期でリクエストを送り、その結果を並行して処理しています。この方法により、I/O待機時間を効率的に活用し、複数のリクエストを同時に処理することができます。
Rustでは、並行処理にスレッドを使うか非同期処理を使うかの選択肢があります。次のような基準で選ぶのが良いでしょう。
両者を理解し、適切な場面で使い分けることで、Rustでより効率的なプログラムを構築できます。
プログラミングを始めたいと思っているそこのあなた、独学よりもプログラミングスクールが断然おすすめです!理由は簡単、続けやすさです。
独学でプログラミングを続けるのは、実はかなりハードルが高いんです。データによると、なんと87.5%もの学習者が途中で挫折しているとか。一方、各プログラミングスクールが公表しているデータによると、受講生の約95%が最後までやり抜いているとのこと。数字を見れば一目瞭然、プログラミングスクールの方が圧倒的に続けやすいんです。
プログラミングスクールには有料と無料のタイプがありますが、その違いは次の通りです:
どちらが自分に合っているか、よく考えて選ぶのが大事です。
プログラミング初心者でも学びやすいと評判の『FREEKS』、その特徴は以下の通り:
なんと、月会費のみで全カリキュラムが受け放題!Java、PHP、HTML/CSS、JavaScriptなど、多彩なプログラミング言語が学べるんです。しかも、AIが質問に自動で答えてくれるシステムも導入済み。
カリキュラムを終了した後には、Freeks経由で未経験者でも取り組める副業案件の受注が可能。実務を通じてスキルを磨き、市場価値の高いエンジニアへの道が開けます。
独学で悩むくらいなら、まずはプログラミングスクールをチェックしてみるのもアリかもしれませんよ!
↓ ↓ こちらをクリック ↓ ↓