変更要約: 初版
3.4優先度逆転とデッドロック
優先度逆転が発生する機序(高優先度タスクが低優先度タスクの保持する資源待ちで足止めされ、さらに中優先度タスクに割り込まれて無期限に遅延する)と、その解決策である優先度継承(priority inheritance)と優先度上限(priority ceiling)の違い、そしてデッドロックの発生4条件(循環待ちを含む)と回避策を学びます。
「優先度の高いタスクは常に低優先度タスクより先に実行される」という直感は、資源の共有が絡むと崩れることがあります。優先度逆転は、まさにこの直感が裏切られる代表的な現象で、実際に宇宙探査機の障害事例としても知られています。組込み設計者には、この現象がなぜ起きるのかを機序レベルで理解し、優先度継承と優先度上限という2つの解決策の違いを正確に使い分ける力が求められます。
3.4.1優先度逆転の発生機序
- 優先度逆転=低優先度タスクLが保持するミューテックス(またはロック)を高優先度タスクHが取得しようとして待たされている間に、優先度Lより高くHより低い中優先度タスクMがLをプリエンプションしてしまい、Hが実質的にMより長く待たされてしまう現象。理論上Hを阻めるのはLだけのはずが、Mによって間接的に阻まれてしまう点が本質。
- この逆転が無期限に継続する場合が特に危険(境界がない優先度逆転/unbounded priority inversion)。同様の中優先度タスクが次々にLをプリエンプションし続ければ、Hの待ち時間には理論上上限が無くなり、Hのデッドライン違反に直結する。
3.4.2優先度継承と優先度上限
- 優先度継承(priority inheritance)=高優先度タスクHがロック待ちになった瞬間、そのロックを保持している低優先度タスクLの優先度を一時的にHと同じ水準まで引き上げるプロトコル。これにより中優先度タスクMはLをプリエンプションできなくなり、Lは速やかにロックを解放してHに明け渡せる。Lはロック解放後、元の優先度に戻る。
- 優先度上限(priority ceiling)=各ロックに「そのロックを取得しうる最高優先度のタスクの優先度」を上限優先度(ceiling)として事前に割り当てておき、ロックを取得したタスクの優先度をそのロックの上限優先度まで即座に引き上げる方式。ロック取得時点で先回りして優先度を引き上げるため、優先度継承よりも早期にブロッキングの連鎖を防げ、複数ロックが絡む場合のデッドロック回避効果も持つ(優先度上限プロトコルの中でも特に高機能な方式は相互のロック取得順序による巡回を防ぐ)。
- 両者の違いの核心:優先度継承はブロッキングが実際に発生してからLの優先度を引き上げる(事後対応)のに対し、優先度上限はロック取得時点で先回りして引き上げる(事前対応)。この違いにより優先度上限の方がブロッキング連鎖の防止力・デッドロック回避力が高い一方、上限値の事前設計(全ロックの上限優先度を洗い出す)という追加コストを伴う。
「優先度逆転は中優先度タスクが低優先度タスクをプリエンプションすることで高優先度タスクの待ちが長引く現象」「優先度継承はブロッキング発生後にLの優先度を引き上げる事後対応」「優先度上限はロック取得時点で先回りして上限まで引き上げる事前対応でデッドロック回避効果もある」が最頻出です。両者を「同じもの」として混同しないこと。
ある組込みシステムで、高優先度タスクH(緊急停止判定・デッドライン厳守)、中優先度タスクM(通信ログ送信)、低優先度タスクL(設定値の読み込み、共有EEPROMへのアクセスをミューテックスで排他)が動いているとします。ある瞬間、Lがミューテックスを取得しEEPROMを読み込み中に、Hがそのミューテックスの取得を試みてブロックされます。ここで対策を入れていなければ、Mが起床してLをプリエンプションし、Mの処理(通信ログ送信、数十ms掛かる)が終わるまでLは再開できず、結果としてHはLの処理時間だけでなくMの処理時間分も余計に待たされる優先度逆転が発生し、Hのデッドラインを外すリスクが生じます。ここで優先度継承を実装すると、Hがブロックされた瞬間にLの優先度がHの水準まで引き上げられるため、Mはもはやプリエンプションできず、LはEEPROM読み込みを速やかに終えてミューテックスを解放し、Hはほぼ即座に実行を再開できます。一方、このシステムにEEPROMアクセス用ミューテックスに加え、共有バッファ用ミューテックスなど複数のロックが存在し、それぞれ異なるタスクの組が競合する場合、優先度継承だけでは「ロックAを待つタスク集合」と「ロックBを待つタスク集合」の絡み合いから複雑な連鎖的ブロッキングが残ることがあります。この場合は優先度上限を採用し、各ロックに「そのロックを取得しうる最高優先度タスクの優先度」を上限として事前設計し、ロック取得と同時に即座にその上限まで優先度を引き上げることで、ロック取得順序に起因する巡回待ち(デッドロックの一因)も含めて防止する、という判断が適切です。単一のロックで単発の逆転を防ぐなら優先度継承で十分だが、複数ロックが複雑に絡み合いデッドロック回避まで求められるなら優先度上限を選ぶ、という設計判断がこの節の核心です。
3.4.3デッドロックの発生条件と回避
- デッドロックは次の4条件が全て同時に成立したとき発生する:
①相互排他(資源は同時に1タスクしか使えない)、
②保持と待ち(資源を保持したまま別の資源を待つ)、
③非横取り(資源は保持タスクが自発的に解放するまで奪えない)、
④循環待ち(タスクAが資源を持つタスクBを待ち、Bがタスクを持つCを待ち…最終的にAに戻る円環ができる)。 - 回避策の代表例:資源獲得順序の統一(全タスクが資源A→Bの順でのみ取得するよう規約化すれば循環待ちが原理的に成立しない)、タイムアウト付き取得(一定時間で取得を諦め保持資源を解放しリトライ)、優先度上限プロトコル(前述の通り複数ロックの巡回取得順序を防ぐ設計にも寄与)。4条件のうちどれか1つでも崩せばデッドロックは発生しないという点が対策の理論的基盤。
| 観点 | 優先度継承 | 優先度上限 |
|---|---|---|
| 引き上げのタイミング | 高優先度タスクが実際にブロックした時点(事後) | ロックを取得した時点で即座に(事前) |
| 引き上げ先 | 待っている高優先度タスクの優先度 | そのロックの上限優先度(事前に設計) |
| 複数ロック環境での効果 | 連鎖的ブロッキングが残る場合がある | 巡回取得順序を防ぎデッドロック回避に寄与 |
ひっかけ: 「優先度継承と優先度上限はどちらも同じ仕組みで呼び方が違うだけである」は誤りです——優先度継承はブロッキング発生後の事後対応、優先度上限はロック取得時点での事前対応という決定的な違いがあります。また「デッドロックの4条件のうち循環待ちだけを崩せば必ず防げる」という理解自体は正しいが、「相互排他条件を崩せば済む」等どの条件を崩すかは資源の性質によって現実的な選択肢が異なる点に注意——資源の性質上そもそも同時共有が不可能(相互排他を崩せない)資源も多く、実務では資源獲得順序の統一(循環待ちを崩す)が最も採用しやすいことが多いです。
3.4.4この節のまとめ
- 優先度逆転は中優先度タスクが低優先度タスクをプリエンプションすることで高優先度タスクの待ちが際限なく延びうる現象
- 優先度継承はブロッキング発生後の事後対応、優先度上限はロック取得時点の事前対応でデッドロック回避効果も持つ
- デッドロックは相互排他/保持と待ち/非横取り/循環待ちの4条件が揃うと発生し、いずれか1つを崩せば防止できる
進捗の記録にはログインが必要です。
理解度チェック
(軽い確認用)Q1. 高優先度タスクHがミューテックス待ちでブロックされている間に、低優先度タスクLをプリエンプションした中優先度タスクMの処理が終わるまでHの実行再開が遅れた。この事象の原因として最も適切なものはどれか。
Q2. EEPROMアクセス用と共有バッファ用の2つのミューテックスがあり、それぞれ異なるタスクの組が競合していて、優先度継承だけでは連鎖的なブロッキングが残ってしまう。この状況で採用すべき対策として最も適切なものはどれか。
Q3. 複数の資源を扱うシステムでデッドロックを防ぎたい。実務上最も採用しやすい対策として適切なものはどれか。

