Haskellによる並列・並行プログラミング読書会 #9 に参加しました #umekitahs

umekitahs.connpass.com

今回から、「第2部 並行 Haskell」に入りました。

個人的な感覚としては、並列の話は馴染みがない話で難しく感じることが多かったですが、並行の話は馴染みがある話で楽に読みすすめられたように思います。並行は要するにマルチスレッドプログラミングの話なので、スマートフォンアプリエンジニアとしては親しみやすいです。

7章 並行制御の基本:スレッドと MVar

新しいスレッドの生成は簡単で、forkIOIO を渡すだけです。

7.1

スレッドを生成して、一定時間後に printf を行うという例です。一定時間待つのは threadDelay で行います。

7.2

スレッド間でデータをやり取りするには、MVar を使います。これは、値がひとつだけ入る箱として使います。takeMVar は値を取り出しますが、値が入るまでブロックします。putMVar は値を入れますが、値が入っていればブロックします。

ちなみに、実行時にデッドロックを検知する仕組みがあって BlockedIndefinitelyOnMVar という例外が投げられるようです。なにそれべんり。詳しいことは後の章で出てくるとのこと。

7.3

別スレッドでログサービスを動かす例です。ログメッセージを渡す手段として MVar を使います。

また、メインスレッド終了前にログサービスを終了する仕組みも実現しています。MVar で終了コマンドを送り、別の MVar でサービス終了を待ちます。

7.4

スレッド間共有データのリードライトロックに MVar を使う例です。共有データを MVar で包んでやることで簡単に実現できてしまいます。

ただし、Haskell の遅延評価には注意が必要です。未評価の値を putMVar すると、ロックは一瞬ですみますが、それが大量に行われると未評価の値の連鎖になってスペースリークの原因になります。一方、putMVar の時に評価を行うようにすると、スペースリークの問題は解決しますが、ロック保持が長くなります。そこで、未評価の値を putMVar した後で seq を使って値を評価する、とやるとうまくいきます。

7.5

MVar を組み合わせてデータ構造を作る例です。

無制限バッファ ChanMVar で作ることができます。writeChanreadChan はわりと簡単にそれっぽく作ることができます。

しかし、Chan を複製する dupChan をそのノリで作ると、元のチャネルと複製したチャネルを両方読み出そうとしたときにデッドロックが発生してしまいます。そこで、take してすぐ put する readMVar が登場します。これを使うと dupChan も実現できます。

ただし、残念ながら unGetChan(読み出し点に値を戻す)はうまく実現できません。

7.6

MVar は CPU 時間の割り当ての意味で公平なスケジューラであるという話が出てきます。ただ、ここの話は分かるような分からないような、ちょっと微妙な感じがしました。

8章 入出力の重ね合わせ

アクションを非同期に実行してその結果を待つ async / wait が、forkIOMVar で簡単に実現できるという話が最初に出てきます。

この async の中で getURL を実行することで、非同期にサーバからデータ取得を行います。

getURL は様々な要因で失敗する場合があるわけですが、そのエラー処理をするために 、Haskell の例外を使うという話になります。

8.1

Haskell に例外なんてあったんだ、という気分ですが、実際のところ組み込みの構文としてはなくて、ライブラリ関数です。Exception 型クラスというのがあります。

throwcatch で例外を「投げる」「捕捉する」という処理が実現されています。他にも、try handle onException throwIO bracket finally なんてのもあります。

8.2

例外について知ったわけですが、getURL は例外を投げます。これを捕捉するような処理をかけば、エラー処理が実現できます。

8.3

複数getURL を実行して、そのうちのどれかが終了するのを待つ、という例です。waitEitherwaitAny でできます。

最後に

今回は珍しく2章分を一気に読んでしまいました。内容がそれほど難しくなかったせいもあるかと思います。次回は9章から。