Semaphore(セマフォ)とは:スレッド同期における仕組みと応用

著者: IT Sectr 公開日: 2026-03-19 読了時間: 8 分

Semaphore(セマフォ)は、カウンタと待機スレッドのキューを通じて共有リソースへのアクセスを制御する同期プリミティブです。Wikipedia, 2024によると、セマフォは1965年にEdsger Dijkstraによってマルチスレッド相互作用の問題を解決するために提案されました。このツールは、クリティカルセクションと同時に作業するスレッドの数を制限できます。

主要ポイント

  • Semaphoreは、許可カウンタを通じてアクセスを制御する同期プリミティブです。
  • バイナリセマフォは値0と1を取り、ブロッキングフラグとして機能します。
  • カウンティングセマフォは、指定された数のスレッドに同時アクセスを許可します。
  • ミューテックスとは異なり、セマフォは所有者スレッドに結びつきません。
  • Deadlockは、セマフォの誤った使用における主要な危険の一つです。

Semaphoreとは?

Semaphoreは、カウンタを使用して共有リソースへのアクセスを制御する同期プリミティブです。この概念は1965年にEdsger Dijkstraによって提案され、オペレーティングシステムにおけるすべての現代的な同期メカニズムの基盤となりました。

定義と目的

セマフォは、wait(acquire)とsignal(release)という2つのアトミック操作を持つ整数変数です。wait操作はカウンタを減少させ、signal操作はカウンタを増加させます。カウンタがゼロに達すると、waitを呼び出したスレッドは、別のスレッドがsignalを実行するまでブロックされます。

セマフォの主な目的は、クリティカルセクションを複数のスレッドによる同時アクセスから保護することです。ミューテックスとは異なり、セマフォは所有者スレッドへのバインドを必要とせず、より広範な調整タスクに適しています。

歴史と理論的基盤

セマフォの概念は、Technische Hogeschool Eindhovenで開発されたTHEオペレーティングシステムの文脈で生まれました。Dijkstraはセマフォを数学的抽象化として形式化し、任意の同期プリミティブを実装するための十分性を証明しました。

セマフォの仕組み

セマフォメカニズムは、2つのアトミック操作と内部待機キューに基づいています。acquireが呼び出されると、スレッドはカウンタ値をチェックし、実行を続行するか、リソースが解放されるまでブロックされます。

カウンタとアトミック操作

セマフォを作成する際、許可カウンタの初期値が設定されます。acquireを呼び出すたびにカウンタが1減少します。その後カウンタが負になると、スレッドはブロックされます。release操作はカウンタを増加させ、待機中のスレッドの一つを起こします。

kotlin
import java.util.concurrent.Semaphore

val semaphore = Semaphore(3)

fun accessResource() {
    semaphore.acquire()
    try {
        println("${Thread.currentThread().name} は動作中")
    } finally {
        semaphore.release()
    }
}

待機キューとスケジューリング

スレッドがゼロカウンタでacquireを呼び出すと、OSはそのスレッドをセマフォのFIFOキューに配置します。スレッドはBLOCKED状態に移行し、CPU時間を消費しません。release呼び出し後、キューの最初のスレッドがRUNNABLE状態に移行し、リソースへのアクセスを取得します。

セマフォの種類

同期理論では、セマフォの2つの主要なタイプが区別されます:バイナリとカウンティングです。タイプの選択は、リソースへのアクセス管理の特定のタスクに依存します。

バイナリセマフォ(Binary Semaphore)

バイナリセマフォは値0と1のみを取ります。動作的にはミューテックスに似ていますが、所有権要件がありません—任意のスレッドがreleaseを実行できます。このようなセマフォは、スレッド間の準備完了フラグやイベントの実装に便利です。

kotlin
val ready = Semaphore(0)

fun producer() {
    Thread.sleep(1000)
    ready.release()
}

fun consumer() {
    ready.acquire()
    println("データ準備完了")
}

カウンティングセマフォ(Counting Semaphore)

カウンティングセマフォは任意の非負の値を取ることができます。これは、複数のインスタンスが利用可能な同種リソースのプールを管理するために使用されます。例えば、5つのネットワーク接続のプール:各acquireが1つの接続を取り、releaseがそれをプールに戻します。

カウンティングセマフォは、外部サービスへのアクセス速度を制限し、スレッドプールを実装するために不可欠です。手動のスレッド管理なしで並列度を正確に制御できます。

パラメータバイナリセマフォカウンティングセマフォ
範囲0または10からN
同時スレッド数1最大N
用途シグナリング、フラグリソースプール、レート制限

Semaphore vs ミューテックス

開発者はしばしばセマフォとミューテックスを混同しますが、両者には根本的な違いがあります。これらの違いを理解することは、プロジェクトで正しい同期メカニズムを選択するために非常に重要です。

所有権の原則

重要な違いは所有権の概念です。ミューテックスは常にどのスレッドがそれを取得したかを把握し、そのスレッドだけが解放できます。セマフォには所有者がありません:任意のスレッドがacquireを呼び出さずにreleaseを呼び出せます。これにより、ミューテックスはデータ保護のためにより安全で、セマフォは調整のためにより柔軟になります。

パフォーマンスと使用シナリオ

実際には、ミューテックスは典型的なシナリオの最適化により、単純な相互排他でより高速です。セマフォはカウンタを維持するための追加オーバーヘッドを必要とします。ただし、並列性を制限したり、プロデューサー-コンシューマーパターンを実装するには、セマフォが不可欠です。

特性Semaphoreミューテックス
所有権所有者なし所有者あり
解放任意のスレッド所有者スレッドのみ
カウンタ0からNバイナリ
ユースケース並列性制限とシグナリングクリティカルセクション保護
再帰なしあり(再入可能)

モバイル開発におけるSemaphore

モバイルアプリケーション開発では、Semaphoreを使用して限られたリソース(ネットワーク接続、ファイル、データベース、ハードウェアコンポーネント)へのアクセスを管理します。現代のプラットフォームは便利な組み込み実装を提供しています。

サーバー接続プール

典型的なユースケースの一つはHTTP接続プールです。プロバイダーのAPIが並列性を制限するため、アプリケーションはサーバーに同時に最大4つのリクエストしか送信できません。初期値4のセマフォは、どのような負荷でも同時リクエスト数が制限を超えないことを保証し、他のスレッドはキューで待機します。

セマフォがない場合、ユーザーアクティビティの急増によりサーバーインフラに突然の過負荷が発生し、タイムアウトや429 Too Many Requestsエラーが発生する可能性があります。Semaphoreはヒューズのように機能し、アクティブなスレッド数に関係なく、厳密に指定された数の同時呼び出しを許可します。

Android向けKotlinのSemaphore

Androidはjava.util.concurrentパッケージからSemaphoreクラスを提供しています。サーバーの過負荷を防ぐために、同時ネットワークリクエストを2スレッドに制限する例を見てみましょう。

kotlin
class ApiClient {
    private val throttle = Semaphore(2)

    suspend fun fetch(url: String): Result {
        throttle.acquire()
        return try {
            httpGet(url)
        } finally {
            throttle.release()
        }
    }
}

iOS向けSwiftのDispatchSemaphore

iOSでは、GCDのDispatchSemaphoreが同じタスクを解決します。開発者はメインスレッドをブロックせずに非同期コードでリソースへのアクセスを同期するためにこれを使用します。

swift
let semaphore = DispatchSemaphore(value: 3)

func processBatch(_ items: [UIImage]) {
    for img in items {
        semaphore.wait()
        DispatchQueue.global().async {
            applyFilter(to: img)
            semaphore.signal()
        }
    }
}

セマフォ使用時の一般的なエラー

最も一般的なエラーは、例外発生時のrelease忘れです。releaseを呼び出す前にスレッドがエラーで終了すると、セマフォは他のスレッドに対して永久にブロックされたままになります。確実な解放のためにtry/finallyまたはdeferを使用してください。2つ目の問題は、異なるスレッドが異なる順序で複数のセマフォを取得する際のデッドロックです。

セマフォの使用パターン

セマフォはデータ保護だけでなく、複雑なマルチスレッドシナリオにおけるスレッド調整にも使用されます。一般的なパターンを理解することで、開発が加速され、同期エラーの可能性が減少します。

実際のプロジェクトでは、セマフォの使用にはいくつかの実績のあるパターンがあります。これらを知ることで、典型的な間違いを避け、信頼性の高いマルチスレッドシステムを構築できます。

レートリミッター(Rate Limiter)

初期値Nとタイマーによる定期的なreleaseを持つセマフォは、APIリクエストのレート制限を実装します。例えば、サービスが毎秒10リクエストを許可する場合:セマフォは10から開始し、各リクエストがカウンタを減少させ、別のTimerTaskが毎秒カウンタを初期値に戻します。これにより、アプリケーションとサーバーの両方を過負荷から保護します。

セマフォを使ったプロデューサー-コンシューマー

古典的なプロデューサー-コンシューマー問題では、2つのセマフォがバッファを管理します:empty(書き込み許可)とfull(読み取り許可)。プロデューサーはemptyでacquire、fullでreleaseを呼び出し、コンシューマーはその逆を行います。このスキームにより、コンシューマーが空のバッファを読み取らず、プロデューサーがバッファをオーバーフローさせないことが保証されます。

この同じスキームは、オペレーティングシステムのバウンデッドバッファ—固定サイズのリングバッファの基礎となっています。モバイルアプリケーションでは、このパターンは画像、ビデオファイル、分析イベントのキュー処理に使用されます。

ネットワークリクエストのスロットリング

セマフォはバックグラウンドサービスでのネットワーク呼び出しのスロットリングに成功裏に使用されています。例えば、分析アプリケーションがサーバーにイベントパケットを送信する場合、ピーク負荷時(アプリ起動、オフライン後の同期)に同時スレッドを制限しないと、同時リクエスト数がサーバー制限を超える可能性があります。初期値3のセマフォはスムーズな送信を保証し、サーバー側のブロッキングを防ぎます。

よくある質問

Semaphoreと通常のカウンタの違いは何ですか?

Semaphoreは単なるカウンタではなく、アトミック操作と待機キューを持つ同期プリミティブです。通常のカウンタはスレッドをブロックせず、複数のスレッドが同時にアクセスした場合のアトミックなインクリメントを保証しません。

セマフォはデッドロックの原因になりますか?

はい、異なるスレッドが異なる順序で複数のセマフォを取得する場合にデッドロックが発生する可能性があります。例えば、スレッドAがS1を取得し、次にS2を取得する一方、スレッドBがS2を取得し、次にS1を取得する場合です。プロジェクト内のすべてのセマフォに対して単一の取得順序を固定してください。

カウンタがゼロの状態でacquireを呼び出すとどうなりますか?

スレッドはブロックされ、待機状態に入ります。別のスレッドがreleaseを呼び出すまでCPU時間を消費しません。JavaではBLOCKED状態、SwiftではGCDによってスレッドが一時停止されます。

Binary SemaphoreとMutexの根本的な違いは何ですか?

主な違いは所有権です。Mutexは所有者スレッドによってのみ解放できます。Binary Semaphoreは任意のスレッドによって解放でき、スレッド間のシグナリングに便利ですが、データ整合性の保護には安全性が低くなります。

カウンタの初期値はどのように選べばよいですか?

初期値はシナリオによって異なります。単一リソースの保護— 1。N接続のプール— N。スレッド間のシグナリングには、コンシューマースレッドがプロデューサーからのシグナルを待つように0を使用します。

まとめ

  • Semaphoreは、1965年にDijkstraによって提案されたカウンタベースの同期プリミティブです。
  • バイナリセマフォは値0と1を取り、カウンティングセマフォは任意の非負の値を取ります。
  • acquireとreleaseの操作はアトミックでスレッドセーフです。
  • ミューテックスとは異なり、セマフォは所有者スレッドに結びつきません。
  • カウンティングセマフォはリソースプールの管理と並列性の制限に使用されます。
  • release忘れはスレッドのハングアップにつながる最も一般的なエラーです。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください