コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
Mutex と Semaphore
Mutex と Semaphore
このレッスンで分かること
- Mutex(相互排除)は「同時に 1 つのスレッドしか入れない錠前」です
- Semaphore は「N 個までのスレッドが同時に入れるカウンタ付き錠前」です
- 両者は似ていますが、所有権の概念と用途が異なります
Mutex と Semaphore とは
Mutex と Semaphore。本レッスンでは、Mutex と Semaphore の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。
Mutex の定義と使い方
Mutex は lock / unlock の対で使い、lock を呼んだスレッドだけがクリティカルセクションに入れます。他のスレッドは lock を呼んだ時点でブロックされ、先のスレッドが unlock するまで待たされます。
Python
import threading
lock = threading.Lock()
counter = 0
def worker():
global counter
for _ in range(100_000):
with lock: # 入室
counter += 1 # ここはクリティカルセクション
# with を抜けるとき自動 unlockC では pthread_mutex_lock / pthread_mutex_unlock、Go では sync.Mutex.Lock / Unlock、Rust では Mutex<T> の lock() ガードを使います。
Semaphore
Semaphore は内部に整数カウンタを持ちます。P(acquire / wait)でデクリメントし、0 以下になれば待機。V(release / signal)でインクリメントし、待機者を 1 人起こします。
Python
import threading
sem = threading.Semaphore(3) # 同時 3 並列まで許す
def download(url):
with sem:
# 同時 3 タスクまで実行可能
do_download(url)カウンタが 1 ならバイナリセマフォとなり、Mutex とほぼ同じ働きをします。3 や 10 にすれば「同時接続数の上限」のような 資源プール の管理ができます。
Mutex と Semaphore の違い
| 観点 | Mutex | Semaphore |
|---|---|---|
| 同時許可数 | 1 | N |
| 所有権 | あり(ロックしたスレッドだけが解放可能) | なし(誰でも release できる) |
| 主な用途 | データ保護 | 資源数の制御、シグナリング |
| デッドロック検出 | やや容易 | 困難 |
「Mutex は錠前」「Semaphore はカード式入退室」というイメージが分かりやすいでしょう。錠前は鍵を持つ人しか開けられず、カードは誰でも入れる代わりに人数制限がある、という対比です。
図解
関連する同期プリミティブ
| 名前 | 役割 |
|---|---|
| Read-Write Lock | 読みは複数並列 OK、書きは排他 |
| Spinlock | スピン待ち(短時間用、CPU を使い続ける) |
| 条件変数(Condition Variable) | 「ある条件が満たされるまで待つ」 |
| Monitor | Mutex + 条件変数のセットを言語が抽象化したもの(Java の synchronized) |
| チャネル | キューと組合せた通信プリミティブ(Go、Rust) |
具体例 (生産者・消費者問題)
Python
import threading, queue
q = queue.Queue(maxsize=10)
def producer():
for i in range(100):
q.put(i) # 内部でセマフォ的に空きを待つ
def consumer():
while True:
item = q.get()
process(item)queue.Queue の内部は Mutex と 2 つのセマフォ(空き、要素)で構成されています。古典的な生産者・消費者問題の教科書例ですが、現代の言語ではこのように標準ライブラリで一発です。
トレードオフ
- ロックの 粒度 は性能に直結します。1 つの巨大ロックで全部守る(粗粒度)と並列性がない、細かく分ける(細粒度)と性能は出るがデッドロックのリスクが上がる
- Spinlock は短い区間で使えば速いですが、長く保持すると他のコアの CPU を 100% 食い潰す悪夢になります
- 多すぎる Mutex は保守不能。「チャネル / アクター で共有を消す」アプローチが現代では好まれます
よくある誤解
- 「Mutex は OS の機能」――半分正解で、最初のロック取得は CPU 命令だけで完結し、競合があったときだけカーネルに頼る Futex(fast userspace mutex)が Linux の実装です
- 「Semaphore は古い」――並列度を上限管理する用途では今でも現役で、
asyncio.Semaphoreなどにも残っています
やってみよう
with lock: を使わずに lock.acquire() だけして release() を忘れたコードを書いてみると、2 回目以降のロック取得で永久に止まることが体感できます。デッドロックの予兆はこの「片付け忘れ」から始まります。
よくある質問
Q. このトピックは実務でどう役立ちますか?
A. DB のクエリ最適化、API 設計、データ構造の選択など、設計判断の根拠になります。表面的にライブラリを使うだけでなく「なぜそれが速いのか」を理解できると、性能問題を未然に防げます。コーディング面接でも頻出のテーマです。
Q. デッドロックを避けるにはどうすればよいですか?
A. 複数の Mutex を取得する順序をすべてのスレッドで統一するのが最も基本的な対策です。「ロック階層」を決め、常に番号の小さい方から取得するルールにするとデッドロックを構造的に排除できます。
Q. バイナリセマフォと Mutex はどう使い分ければよいですか?
A. 「ロックを取得したスレッドだけが解放する」という所有権が必要なら Mutex を選びます。「あるスレッドが acquire し、別のスレッドが release する」シグナリング用途や、同時実行数を N に制限する場合は Semaphore が適しています。
次のレッスン
次は デッドロック で、複数のロックが互いを待ち合って処理が永久に止まる現象とその回避策を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- Mutexとセマフォ の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. Mutexとセマフォ とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン