FE EXAM

OSの排他制御(クリティカルセクションとロック)

共有リソースへの同時アクセスを防ぐ仕組み。ロック有り/無しで結果がどう変わるかを比較します。

MUTEX SIMULATOR
P1
P2
共有
ロック
シナリオ
排他制御なし(壊れる例)
ロックを使わずに 2 つのプロセスが共有変数 counter を 1 増やそうとすると、結果が壊れる。
ロック
なし
結果
0
シナリオ
ステップ1 / 6
STEP 1/6初期状態共有変数 counter = 0。P1 と P2 はそれぞれ counter を 1 増やそうとしている。期待される結果は counter = 2。
解説

📌
排他制御とは — 共有資源を守る仕組み

P1P2同時にアクセス共有リソース1度に1人だけ!

排他制御(mutual exclusion)とは、複数のプロセスやスレッドが共有リソースに同時にアクセスして壊さないように制御する仕組みです。

身近な例えだと、「公衆トイレの個室の鍵」のような仕組みです。トイレ(クリティカルセクション)は同時に 1 人しか使えないので、入る人は鍵を掛ける(ロック)。次に来た人は外で待つ。中の人が出てきて鍵を外す(解放)と、ようやく次の人が入れる。

上のツールで「排他制御なし」と「排他制御あり」を切替えると、ロックの有無で結果がどう変わるかを比較できます。

📌
クリティカルセクション — 排他しなければならない区間

クリティカルセクション(critical section)とは、共有リソースにアクセスするコード区間のことです。ここで複数プロセスが同時に動くとデータが壊れるため、同時に 1 プロセスだけが入れるように排他制御をかける必要があります。

クリティカルセクションが守るべき3つの性質:
相互排除(Mutual Exclusion):同時に 1 プロセスだけが入れる
進行性(Progress):誰も入っていないなら、待っているプロセスは入れる
有限待ち(Bounded Waiting):永遠に待たされない(飢餓を防ぐ)

上のツール「排他制御あり」のステップ 3 で、P1 がクリティカルセクションに入っている間、P2 が「ロック待ち」になって待たされる様子を確認できます。これが相互排除の働きです。

📌
ロック(mutex) — 鍵による排他

① lock()🔒取得② 処理クリティカルセクション③ unlock()🔓解放

排他制御で最も基本的な道具がロック(mutex = mutual exclusion)です。鍵のように取得・解放を 1 対で使います。

正しい使い方:
① lock():ロックを取得(誰かが持っていれば待機)
② クリティカルセクションを実行:共有リソースに安全にアクセス
③ unlock():ロックを解放(待っていた次のプロセスが目を覚ます)

注意:unlock を忘れると他のプロセスが永遠に待たされる大問題が起きます。例外発生時にも必ず unlock するよう、try-finally や RAII 等の仕組みで保証するのが定石です。

📌
排他制御がないと何が起きるか

ロックなしで複数プロセスが共有変数を更新すると、更新が消失するロストアップデート」が発生します。これは「OSのスレッド」ページで扱ったレースコンディションの典型例です。

上のツール「排他制御なし」シナリオで、2 つのプロセスが counter を +1 しても 1 にしかならない例が確認できます。原因:
・P1 が counter(=0)を読む → レジスタに 0
・P2 も counter(まだ=0)を読む → レジスタに 0
・P2 が +1 して書き戻す → counter = 1
・P1 が古い値で +1 して書き戻す → counter = 1(P2 の更新が上書きされた)

この問題は銀行口座の残高更新などでよく例として挙げられます。同時に 1 万円ずつ振り込んだのに、結果が 1 万円増だけになると大事件です。

📌
どこで使われているか

排他制御は身近なソフトウェアで広く使われています。


データベースのトランザクション:レコードの同時更新を制御
ファイル書き込み:複数プロセスが同じファイルを書く際の整合性
プリンタ共有:複数の印刷ジョブが混ざらないよう順番に処理
Web アプリのカウンター・在庫管理:複数リクエストの並列処理
マルチスレッドプログラム:共有データ構造の操作(キュー、マップなど)

排他制御が必要かどうかの判断は「共有リソースに書き込みが発生するか」がポイント。読み取りだけなら不要、書き込みがあれば必要、と覚えておくと迷いません。

📌
排他制御の落とし穴

ロックは便利ですが、扱いを誤るとさらに厄介な問題を生みます。


デッドロック:複数プロセスが互いのロックを待ち合い永遠に止まる(次ページで詳説)
飢餓(starvation):特定のプロセスがいつまでもロックを取れず実行されない
性能低下:ロックが衝突すると並列性が下がる。ロックの粒度を細かくしすぎても粗くしすぎても問題
unlock 漏れ:例外発生で unlock しないと永久にロックされたまま

特にデッドロックは実務でも重要なので、次のページ「OSのデッドロック」で詳しく学びましょう。

📌
なぜロックが必要か — レースコンディション

時間 →P1read(0)calc: 0+1write(1)P2read(0)calc: 0+1write(1)本来 2 になるはずが 1 のまま(P1 の更新が消えた)

なぜロックが必要なのか。それは、CPUが複数の処理を細かく交互に切り替えながら動かすため、「読む → 計算 → 書く」という一連の操作が途中で割り込まれることがあるからです。

上の図で何が起きているか見てみましょう。共有の変数(最初は 0)を、P1 と P2 がそれぞれ +1 しようとしています。
・P1 が 0 を読んでいる間に、P2 も同じ 0 を読んでしまう
・両方が「0 に 1 足して書く」ので、結果は 1 が2回書かれるだけ
本来 2 になるべきが、1 になってしまう

この「どちらが先に動くかで結果が変わる、ギャンブルのような状態」をレースコンディション(=競合状態)と呼びます。ロックはこの競合を防ぐために、「読む〜書くまでの一連の操作を、他のプロセスに割り込まれない1かたまりにする」道具です。銀行口座に例えると、同じ残高を2人が同時に読んで別々に引き出したら、どちらかの引き出しが消えてしまうのと同じ問題です。

📌
デッドロック — 互いを永遠に待ち続ける状態

P1ロックA保持P2ロックB保持ロック Aロック BB を待つA を待つ互いを待ち合い、永遠に進まない(デッドロック)

デッドロックとは、複数のプロセスが互いの持っているロックを待ち合って、全員が身動き取れなくなる状態のことです。ロックを正しく使わないと起きます。

上の図の例で流れを追ってみましょう。
・P1 がロック A を取得して作業中。次に ロック B も必要になった
・P2 がロック B を取得して作業中。次に ロック A も必要になった
・P1 は「B が空くのを待つ」→ しかし B は P2 が持っている
・P2 は「A が空くのを待つ」→ しかし A は P1 が持っている
どちらも永遠に待ち続け、一切進まない

身近なたとえで言うと、狭い一本道ですれ違いができず、お互いが「相手が引いてくれるまで待つ」と主張して動けなくなった2台の車のようなものです。デッドロックを防ぐ基本的な方法は「ロックを取る順番を全プロセスで統一する」ことです。たとえば「必ず A → B の順に取る」と決めれば、図のような待ち合いは起きません。

練習問題

🎯
基本情報技術者 練習問題

Q1.排他制御の目的として最も適切なものはどれか。
A.プロセスの優先度を制御する
B.共有リソースへの同時アクセスを防ぎ、データの一貫性を保つ
C.CPU の使用率を上げる
D.メモリ使用量を減らす
Q2.クリティカルセクションの説明として正しいものはどれか。
A.プロセスが必ず実行する初期化処理
B.CPU の使用率が最も高い区間
C.共有リソースにアクセスする、同時に実行してはいけないコード区間
D.プロセスが終了する直前の処理
Q3.ロックを使った排他制御の流れとして正しいものはどれか。
A.ロック取得 → クリティカル実行 → ロック解放
B.クリティカル実行 → ロック取得 → ロック解放
C.ロック解放 → ロック取得 → クリティカル実行
D.クリティカル実行 → ロック解放 → ロック取得

関連コンテンツ