「セマフォ」は、並行処理のリソース管理や同期に使用される重要な機能の一つです。
この記事では、Rustのコードを使ってセマフォの概念、使い方、実際の応用例について解説してみます。
セマフォとは何か
セマフォの基本概念
セマフォは、共有リソースへのアクセスを制御するための同期プリミティブです。例えると「許可証(パーミット)」の管理システムのようなものです。リソースにアクセスするためには許可証が必要で、利用可能な許可証の数は限られています。
セマフォには以下のような特徴があります:
- カウンターの維持: セマフォは内部でカウンター(許可証の数)を持っています
- 複数アクセスの許可: ミューテックスとは異なり、複数の同時アクセスを許可できます
- ブロッキング機能: 利用可能な許可証がない場合、新たに利用可能になるまで待機します
セマフォが解決する問題
セマフォは以下のような状況で特に有用です:
- リソース制限: 同時に実行できるタスクの数を制限したい場合
- 生産者-消費者パターン: 生産されたアイテムと消費されるアイテムの同期
- ファイルハンドルやネットワーク接続など限られたリソースの管理
- APIsへのリクエスト数の制限(レート制限)
Rustにおけるセマフォの実装
Rustでは、標準ライブラリには現在セマフォの実装が含まれていません。かつてはstd::sync::Semaphore
が存在していましたが、非推奨となり削除されました。現在は主に以下のクレートでセマフォを利用することができます:
-
tokio::sync::Semaphore
- 非同期プログラミング向けのセマフォ - サードパーティのセマフォ実装(
std-semaphore
やsimple-semaphore
など)
この記事では、最も広く使われているtokio
のセマフォ実装に焦点を当てます。
tokio::sync::Semaphoreの基本的な使い方
セマフォのインスタンス化
まず、セマフォのインスタンスを作成します:
use tokio::sync::Semaphore;
// 3つの許可証を持つセマフォを作成
let semaphore = Semaphore::new(3);
この例では、最大3つのタスクが同時にリソースにアクセスできるセマフォを作成しています。
許可証の取得と解放
セマフォから許可証を取得するには以下の方法があります:
async fn example() {
let semaphore = Semaphore::new(2);
// 許可証を非同期に取得(利用可能になるまで待機)
let permit = semaphore.acquire().await.unwrap();
// リソースを使用...
// permitがスコープを抜けると自動的に解放される
drop(permit); // 明示的に解放することも可能
// 待機せずに許可証を取得(利用可能でなければエラー)
match semaphore.try_acquire() {
Ok(permit) => {
// リソースを使用...
}
Err(e) => {
println!("許可証を取得できませんでした: {:?}", e);
}
}
}
複数の許可証の取得
単一の許可証だけでなく、複数の許可証を一度に取得することも可能です:
async fn example_multiple() {
let semaphore = Semaphore::new(5);
// 2つの許可証を一度に取得
let permit = semaphore.acquire_many(2).await.unwrap();
// 現在利用可能な許可証の数を確認
println!("利用可能な許可証: {}", semaphore.available_permits());
// permitがスコープを抜けると2つの許可証が解放される
}
セマフォ使用時の注意点
セマフォの注意点:
デッドロックの防止
セマフォを使用する際は、デッドロック(相互ロック)に注意が必要です。特に以下のような状況に気をつけましょう:
- 複数のセマフォを使用する場合は、常に同じ順序で取得する
- ミューテックス内でセマフォを待つことは、典型的なデッドロックパターンなので避ける
メモリリーク
非同期処理では、許可証の取得を待っているタスクが何らかの理由でキャンセルされると、セマフォの状態が不整合になる可能性があります。tokio
のセマフォはキャンセル安全に設計されています。
所有権に関する考慮
スレッド間でセマフォを共有する場合、Arc
(アトミック参照カウンタ)で包む必要があります。また、許可証を別のスレッドに移動する場合は、acquire_owned
メソッドを使用します:
let permit = Arc::clone(&semaphore).acquire_owned().await.unwrap();
tokio::spawn(async move {
// permitを所有するタスク
// ...
// タスクが終了するとpermitがドロップされる
});
まとめ
この記事では、Rustにおけるセマフォの基本概念、tokio::sync::Semaphore
について解説しました。セマフォについて理解が深まれば幸いです。