例外とかがナチュラルに出てくると Haskell っぽくない感じがするのは気のせいでしょうか。
9章 キャンセルとタイムアウト
あるスレッドが他のスレッドに「割り込む」ことをどう扱うか、という話。ここでいう割り込みは、処理のキャンセルだったり、タイムアウトだったりのことです。
Haskell では、キャンセルフラグのポーリングといった処理はできません。そうではなく、割り込みから保護されるクリティカルセクションを設けることになります。
9.1 非同期例外
通常の throw
や throwIO
で投げる例外は、特定の処理を行ったときに発生する同期例外です。それとは異なり、どこでも発生する例外(ユーザの操作など)は非同期例外です。非同期例外は throwTo
で発生させます。
例外を処理する方法は、同期例外も非同期例外も同じで、catch
などを使います。
9.2 非同期例外のマスク
mask
で非同期例外の伝達を遅延することができます。つまり、クリティカルセクションを作ることができます。mask
に渡す restore
関数の部分で非同期例外が発生し、それ以外の部分では発生しません。
ただし、takeMVar
などはそれ自身が割り込み可能な操作です。takeMVar
はブロックする場合がありますが、ブロックしている間は非同期例外を受け取ります(mask
の中であっても)。
9.3 bracket
例外を処理する bracket
関数は、実は内部で mask
を行っています。直接 mask
を使うよりも、bracket
を使う方が簡単かも。
9.4 チャネルに対する非同期例外の安全性
mask
を実際に使う例です。どこをマスクすべきなのかは意外と難しい・・・。マスク範囲を狭くしたいのですが、そうすると、マスクが必要な箇所をマスクできてない、ということになりがち。
9.5 タイムアウト
アクションにタイムアウトを設ける例です。
基本的な考え方は、新しいスレッドを生成して、それが一定時間待機した後で元スレッドに Timeout
例外を投げる、というものです。そのスレッドは、元スレッドの操作がタイムアウト前に終わった場合は例外を投げてはいけません。このため、元スレッドは操作終了後に例外を投げて子スレッドを殺します。
このセクションの最後に、子スレッドと親スレッドが同時に例外を投げるとどうなるか、という話が書かれていますが、文章が複雑でイマイチ意味がわからない感じです・・・。
9.6 非同期例外の捕捉
例外ハンドラの処理中に別の例外が割り込む可能性がある、という話です。まあ単純に、例外ハンドラはマスクしましょう、ということになるかと思います。注意点として、例外ハンドラから他の関数を呼び出すとそれがマスクの内側に残ってしまいます。
9.7 mask と forkIO
forkIO
は親スレッドのマスク状態を継承したスレッドを生成する、という話です。9.5 で子スレッドのマスク状態はどうなってるのだろう? という疑問がありましたが、ここで解決しました。
9.8 非同期処理に関して
9章のまとめです。非同期例外はなかなか面倒なやつです。ただ、IO モナドを含まない Haskell コードは安全なので、IO の箇所のみ気をつければ十分、というのは良い点かと思います。
最後に
7章や8章はそんなに難しくない気がしていましたが、9章は意外と難しい印象でした。