導入:なぜ「非同期例外」を意識する必要があるのか
並行プログラミングをしていると、自分のコードが順調に動いていても、突然外部から「強制終了」の命令が飛んでくることがあります。これが「非同期例外」です。
普通の例外と異なり、コードのどこで発生するかわかりません。もし、ファイルを開いた直後やメモリを確保した直後にこの例外が飛んできたらどうなるでしょうか?リソースが解放されず、メモリリークやファイルの破損を引き起こします。これを防ぐための「割り込み禁止区間」の作り方を解説します。
基礎知識:非同期例外とは何か
非同期例外とは、プログラムの論理的な流れとは無関係に、他のスレッドから「お前はもう終了だ!」と送りつけられる例外です。
初心者の方が特に注意すべきは、「計算の途中に割り込まれる」という点です。例えば、DB接続を確立して、その後にデータを書き込む処理があるとします。確立した直後に非同期例外が来ると、接続を閉じる処理がスキップされ、DBのコネクションが枯渇してしまうのです。
実装:bracketとmaskによる防御
この問題を解決するために、関数型言語(Haskellなど)では「bracket」や「mask」という道具を使います。
1. bracket: 「確保」「実行」「解放」の3ステップを定義します。万が一途中で例外が起きても、必ず「解放」処理が実行されるよう保証されます。
2. mask: 一時的に「今は非同期例外を受け付けないよ!」と宣言する区間を作ります。これにより、リソースの準備中に割り込まれるのを防ぎます。
サンプルプログラム:安全なファイル操作
以下は、非同期例外が発生しても確実にファイルを閉じる実装例です。
// ファイル操作を安全に行うためのサンプルコード
import Control.Exception (bracket, mask, restore)
import System.IO
// 安全なファイル書き込み関数
safeWriteFile :: FilePath -> String -> IO ()
safeWriteFile path content =
// bracketの第1引数でリソース確保、第3引数で解放、第2引数で処理を行う
bracket
(openFile path WriteMode) // 1. ファイルを開く
(\handle -> hClose handle) // 3. 例外発生時でも必ず閉じる
(\handle -> do // 2. 処理本体
// maskを使って、重要な準備区間を保護する
mask $ \restore -> do
hPutStrLn handle content
putStrLn “書き込みが完了しました”
)
応用・注意点:現場での心得
現場で最も陥りやすいバグは、「maskをかけすぎて処理が終了しなくなる」ことです。
maskで保護した区間が長すぎると、その間はスレッドが外部からの終了要求を無視し続けます。必ず「必要最小限の範囲」だけをmaskするようにしてください。また、ライブラリを使用する際は、その関数が「例外安全(Exception Safe)」に設計されているかドキュメントを確認する癖をつけましょう。
「例外はどこで起きるかわからない」という前提に立ち、リソースの管理を言語の機能に任せる。これが、堅牢な並行プログラムを書くための第一歩です。

コメント