1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

セマフォとミューテックスの違いを丁寧に解説 — 「排他制御」と「同期」は何が違うのか

1
Posted at

この記事で伝えること

マルチスレッドプログラミングを学ぶと必ず出てくる「ミューテックス(Mutex)」と「セマフォ(Semaphore)」。どちらも「複数のスレッドの動きを制御する仕組み」という点では似ていますが、目的も使い方も実は異なります。

この記事では、

  • ミューテックスとセマフォがそれぞれ何のために存在するのか
  • 「排他制御」と「同期」という2つの異なる問題
  • 具体的なコード例でどう使い分けるのか

を、丁寧に解説していきます。「なんとなく違いは知っているけど、いざ聞かれると説明できない」という状態から抜け出せる内容を目指します。

基礎知識の解説

そもそも何のためにあるのか

複数のスレッドが同時に動くプログラムでは、共有されているデータやリソースに複数のスレッドが同時にアクセスすると、意図しない結果(レースコンディション)が発生します。

例えば、2つのスレッドが同時に「カウンターを1増やす」処理をすると、以下のような競合が起きます。

スレッド1: カウンターを読む(0)
スレッド2: カウンターを読む(0)
スレッド1: 1を足して書き込む(1)
スレッド2: 1を足して書き込む(1)  # 本来は2になるべきなのに1のまま

これを防ぐための仕組みが「排他制御」と「同期」です。ミューテックスは主に前者、セマフォは両方に使われますが、それぞれ得意な役割が異なります。

ミューテックス(Mutex)とは

Mutexは "Mutual Exclusion"(相互排他)の略で、その名の通り「一度に1つのスレッドしかアクセスできないようにする」ための仕組みです。

特徴は以下の通りです。

  • 状態は「ロック中」か「アンロック中」の2値だけ
  • ロックしたスレッドだけがアンロックできる(所有権の概念がある)
  • 用途は「1つの共有リソースを1スレッドずつ順番に使わせたい」場合

イメージとしては「トイレの鍵」に近いです。入った人が鍵を閉め、出るときに自分で開ける。他の人は開けられません。

import threading

lock = threading.Lock()
counter = 0

def increment():
    global counter
    with lock:  # ロック取得。処理が終わると自動でアンロック
        counter += 1

セマフォ(Semaphore)とは

セマフォは「同時にアクセスできるスレッド数を、任意の数(N)まで許可する」仕組みです。内部的にはカウンタを持っていて、リソースを使うたびにカウンタを減らし、使い終わったら増やします。

特徴は以下の通りです。

  • カウンタが0より大きい間は複数のスレッドが同時に進入できる
  • 所有権の概念がない(ロックしたスレッド以外でもカウンタを操作できる)
  • 用途は「同時接続数を制限したい」「あるイベントの発生をスレッド間で知らせたい」場合

イメージとしては「駐車場のゲート」です。空きが3台分あれば3台まで同時に入れますが、満車になると次の車は待たされます。

import threading

# 同時に3スレッドまでアクセスを許可
semaphore = threading.Semaphore(3)

def access_resource():
    with semaphore:
        # ここに最大3スレッドまで同時進入できる
        do_something()

Nを1にすればミューテックスと似た挙動になりますが、「所有権がない」という点が本質的に異なります。つまりセマフォは、ロックしたスレッドとは別のスレッドが「解放(release)」を行うこともできます。この性質が、後述する「同期」の用途で活きてきます。

排他制御と同期という2つの問題

ここが混同されやすいポイントです。

  • 排他制御(Mutual Exclusion): 「同時に1つだけ」を保証したい問題。ミューテックスが得意。
  • 同期(Synchronization): 「あるスレッドの完了を、別のスレッドが待つ」問題。セマフォが得意。

例えば「生産者・消費者問題」(生産者スレッドがデータを作り、消費者スレッドがそれを処理する)では、単なる排他制御だけでなく「データがまだ無いときは消費者を待たせる」という同期の仕組みが必要です。これはミューテックスだけでは表現しづらく、セマフォ(カウンティングセマフォ)が自然にフィットします。

具体例・実際の動作

生産者・消費者問題をセマフォで表現すると、以下のようになります。

import threading
import queue
import time

buffer = queue.Queue(maxsize=5)
empty_slots = threading.Semaphore(5)  # 空きスロット数
filled_slots = threading.Semaphore(0)  # データが入っているスロット数

def producer():
    for i in range(10):
        empty_slots.acquire()  # 空きが無ければ待つ
        buffer.put(i)
        print(f"生産: {i}")
        filled_slots.release()  # データが1つ増えたことを通知
        time.sleep(0.1)

def consumer():
    for _ in range(10):
        filled_slots.acquire()  # データが無ければ待つ
        item = buffer.get()
        print(f"消費: {item}")
        empty_slots.release()  # 空きが1つ増えたことを通知
        time.sleep(0.2)

threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()

ここでは empty_slotsfilled_slots という2つのセマフォが、「バッファの空き状況」と「データの有無」という2つの状態をそれぞれ表現し、生産者と消費者の速度差を自動的に調整しています。これはミューテックスだけでは実現しづらい、セマフォならではの使い方です。

筆者の考え・所感

個人的には、ミューテックスとセマフォの違いを理解する一番のコツは「所有権があるかどうか」だと思っています。ミューテックスは「鍵をかけた本人しか開けられない」という強い制約があるからこそ、単純な排他制御に向いています。一方セマフォは所有権が緩い分、生産者・消費者のような「異なるスレッド間でのシグナリング」に自然に使えます。

実務では、単純な共有変数の保護にはほぼミューテックス(あるいはRWロックなどの派生形)を使い、「接続数の上限を決めたい」「キューの空き/詰まりを制御したい」といった場面でセマフォを選ぶ、という使い分けをしています。名前が似ているせいで最初は混同しがちですが、「排他か同期か」という視点で考えると、どちらを選ぶべきかが自然と見えてくると感じています。

また、Pythonの threading.Lock はGIL(Global Interpreter Lock)の存在もあって効果を実感しにくい場面もありますが、I/Oバウンドな処理やファイル・ネットワークリソースへのアクセス制御では依然として重要な仕組みです。他言語(Go・Java・Swiftなど)でも考え方はほぼ共通するので、一度きちんと理解しておくと応用が効きます。

まとめ

  • ミューテックスは「1つのリソースを1スレッドずつ排他的に使う」ための仕組みで、所有権の概念がある
  • セマフォは「N個まで同時アクセスを許可する」カウンタ式の仕組みで、所有権がなく、異なるスレッド間の同期(シグナリング)にも使える
  • 「排他制御」なのか「同期」なのかを見極めることが、どちらを使うべきかの判断基準になる
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?