basic study for erlang #2
TRANSCRIPT
第二回 Erlang 基礎勉強会M.Ikuta (@cooldaemon)
2010/7/28
本日の流れ
• 準備(心構え)
• Erlang プロセス概要
• プロセスのデザインパターン
• ハンズオン / フリートーク
本日の流れ
• 準備(心構え)
• Erlang プロセス概要
• プロセスのデザインパターン
• ハンズオン / フリートーク
• ちょっとしたコツを覚えて簡単に Erlang を習得
• 的を絞って効率的に習得
• Erlang は、サーバ (ミドルウェア)を書く為の DSL
• 全ての機能は、サーバを書く為にある(と割り切る)
• DSL だから言語仕様は小さい(と思い込む)
• ビジネスロジックの記述に向いていない
• 「Erlang は簡単だ!」の裏には・・・
• リスト、再帰、イミュータブルは初歩の初歩
• アクターモデルは、解りやすくて最高
• コツを掴めば Erlang は簡単だ!
• リスト、再帰、イミュータブル
• 第一回 基礎勉強会(前回)の範囲
• コツを掴めば Erlang は簡単だ!
• アクターモデルの基礎
• 第二回 基礎勉強会(今回)の範囲
• という事で今回はアクターモデルの話。ただし…
• メモリや GC、スケジューラなどの話は無し
• Supervisor, gen_*, proc_lib の話も無し
• という事で今回はアクターモデルの話。ただし…
• プロセスの概要と利用方法(配置)の話が中心
本日の流れ
• 準備(心構え)
• Erlang プロセス概要
• プロセスのデザインパターン
• ハンズオン / フリートーク
• プロセスは、小さく単純な逐次処理の塊
• 逐次処理であるため、並列処理の複雑さが無い
• 多数のプロセスが協調して大きな仕事を行う
• プロセス同士は粗結合
• 共有リソースは基本持たない
• メッセージパッシングで連絡を取り合う
• オブジェクトのメソッド呼び出しに似ているが…
• メッセージ送信は非同期でありキューに溜まる
• メッセージに対応するハンドラが無い事もある
• 関数評価よりコストが高い
• オブジェクトのメソッド呼び出しに似ているが…
• オブジェクトを作る感覚で処理の塊をプロセス化すると不必要な複雑さや負荷を招く
• 並列化不要な箇所を並列化しない
• どのノードにプロセスがあるか意識しなくて良い
• プロセス ID 内にノード情報が含まれている
• メッセージ送受信、監視(link/1, monitor/2) の方法が共通
• 監視プロセスの起動ノードを物理的に分散
• どのノードにプロセスがあるか意識しなくて良い
• 信頼できる閉じたネットワーク内であれば
Erlang のアクターモデルは非常に強力
• プロセス情報の確認
• i/0,1, ni/0
• process_info/1,2
• pman:start/0
• etop, getop
• プロセス情報の確認
• Erlang Shell で ni() を評価すると nodes(). で取得できるノードのプロセス一覧も取得できる
• Mac で pman, getop を実行すると X の起動に時間が掛かり、タイムアウトする事がある
• プロセス情報の確認
• etop, getop の PATH は code:lib_dir/1 で取得
`erl -noshell -eval 'io:format("~s", [code:lib_dir(observer)]).' -s init stop`\/priv/bin/etop -node foo@bar
• プロセス情報の確認
• Msgs, MsgQ は、キュー内のメッセージ数
• 選択受信すると溜まりがち
• 溜まると負荷になる(選択受信が重くなるので負のスパイラル)
• プロセス情報の確認
• Reds
• Reductions の略
• コンテキストスイッチに利用
• プロセス情報の確認
• Reds
• 値を変更する事や、process_flag/2 で優先順位を設定する事もできるが非推奨
• プロセスの作成
• spawn/1,2,3,4
• spawn_link/1,2,3,4
• spawn_monitor/1,3
• spawn_opt/2,3,4,5
• プロセスの作成
• spawn/1,3
• spawn(fun () -> ok end).
• 新たにプロセスを生成し、そのプロセス上で引数で指定された関数を実行する
• プロセスの作成
• spawn/1,3
• よほどの事(プロセス数の上限など)が無ければ、プロセスの生成は成功する
• 起動した後、即座に終了する事はある
• プロセスの作成
• spawn/1,3
• try catch で囲う、ありがちなミス
• 別プロセス上の例外は catch できない
• プロセスの作成
• spawn/2,4
• リモートノード上で、新たにプロセスを生成
• リモートノードのプロセスを、自ノードのプロセスと同様に扱える(register/1 は使えない)
• プロセスの作成
• spawn_link や spawn_monitor など…
• spawn と、それ以外(linkなど)を不分割に実行
• spawn 直後にプロセスが終了し、その後に
リンクされるという事がない
• プロセスの作成
• プロセス数の上限を調べる
• erlang:system_info(process_limit).
• プロセス数の上限を設定する
• erl +P 13421772
• プロセスに名前を付ける
• register/2
• unregister/1
• whereis/1
• global モジュール(今回は説明しない)
• プロセスに名前を付ける
• register/2
• register(Name, Pid).
• プロセス ID を引き渡す手間を省略できる
• プロセスに名前を付ける
• register/2
• ノード内で一意であり、同じ内容のプロセスを多重起動するケースでは使えない
• 常に起動し続ける(もしくは長生きする)事を想定するプロセスに名前を付ける
• プロセスに名前を付ける
• register/2
• 名前を付ける行為は、利用者に依存を強要する事になる
• プロセスに名前を付ける
• whereis/1
• whereis(Name).
• プロセス名からプロセス ID を取得
• プロセス ID を引数とする関数の評価に利用
• プロセスに名前を付ける
• unregister/1 は簡単なので割愛
• global モジュールは、今回の対象外であるため各自で調査
• メッセージの送信
• ! で送信
• (Pid | Name) ! Message.
• 非同期(必要な結果は別途受信)
• 成功・失敗は不明(エラー処理不要)
• メッセージの送信
• メッセージ送信はデータのコピー
• プロセス間でデータ共有をしない
• サイズの大きいメッセージ送信は負荷になるので送らない
• メッセージの送信
• 関数の評価よりコストが高い
• sample/send_message_vs_call_function.erl
• メッセージ送信回数を減らし、一回のメッセージ送信で、なるべく多くの仕事を行う
• メッセージの送信
• メッセージ送信の成功失敗は不明
• メッセージ送信は絶対成功すると考える
• 存在しないプロセスにもメッセージは送れる
• 送信先プロセスに依存しない
• メッセージの送信
• ただし…存在しないプロセスの名前にメッセージを送ると例外が発生する
• プロセスの名前を利用する事は、そのプロセスに依存すると言う事
• プロセスに名前を付ける意味を考える
• メッセージの受信
• receive で受信
• receive Message -> ok end.
• 同期(メッセージを受信するまで待つ)
• タイムアウトを指定する事も可能
• メッセージの受信
• タイムアウト 0 をキュー清掃に利用
• 清掃時(_ 受信含む)は、ログを残す癖を付ける
• 送るべきではないメッセージを送っていたり、送り先を間違えたりしている証拠となる
• メッセージの受信
• キューに溜まってるメッセージをパターンマッチで選択受信できる
• キューにメッセージが溜まりすぎると、選択受信の効率が落ちる
• メッセージの送受信
• 送信後、結果が必要な場合、下記をメッセージに含める事を推奨
• 結果を返せるよう、送信元のプロセス ID
• 他のメッセージを受けないよう、メッセージ毎にユニークなキー(make_ref/1 など)
• 監視(エラーハンドリング)
• link/1
• erlang:monitor/2
• exit/1,2
• process_flag/1
• 監視(エラーハンドリング)
• link/1
• link(Pid).
• link/1 によるリンクは相互リンク
• 片方が終了すると、もう片方も終了
• 監視(エラーハンドリング)
• link/1
• 存在しないプロセス ID を引数にすると例外が発生する
• 監視(エラーハンドリング)
• erlang:monitor/2
• erlang:monitor(process, Pid).
• 監視を行う(片方向)
• 監視(エラーハンドリング)
• erlang:monitor/2
• 監視中のプロセスが終了するとメッセージを受信
• {‘DOWN’, Reference, process, Pid, Reason}
• 監視(エラーハンドリング)
• link/1, erlang:monitor/2 は、リモートノードのプロセスを監視できる
• ローカルノード上にプロセスがある場合と同様のコードで良い
• 監視(エラーハンドリング)
• exit/1
• exit(Reason).
• 自プロセスを終了
• リンク先に終了シグナルをブロードキャスト
• 監視(エラーハンドリング)
• exit/1
• normal と kill は特別な終了シグナル
• 監視(エラーハンドリング)
• exit/1
• 実行するべき処理が無いために終了した場合のシグナルが normal
• リンクしているプロセスは終了しない
• exit(normal). でも同様
• 監視(エラーハンドリング)
• exit/1
• kill を受信すると、リンクしているプロセスには killed をブロードキャストする
• 監視(エラーハンドリング)
• exit/2
• exit(Pid, Reason).
• Pid へ終了シグナルを送信
• 監視(エラーハンドリング)
• exit/2
• 自プロセスは終了しない
• 実は、終了の偽装
• 監視(エラーハンドリング)
• process_flag/2
• process_flag(trap_exit, true).
• システムプロセスに昇格
• 監視(エラーハンドリング)
• process_flag/2
• システムプロセスは、リンク先のプロセスが終了した際、終了せずにメッセージを受信
• {‘EXIT’, Pid, Reason}
• 監視(エラーハンドリング)
• process_flag/2
• normal もメッセージとして受信できる
• kill の場合は強制的に終了する
• 監視(エラーハンドリング)
• 監視を行う事で様々なエラー処理が可能
• エラーログを残す
• 終了したプロセスを再起動
• 関連プロセスを一斉終了
• 監視(エラーハンドリング)
• 監視の仕組みを利用し、監視の木を構成する事を、本勉強会ではプロセス配置と呼ぶ
本日の流れ
• 準備(心構え)
• Erlang プロセス概要
• プロセスのデザインパターン
• ハンズオン / フリートーク
• プロセスを用いたコードを記述する上で、よく見かけるパターンを幾つか紹介
• 基本的には OTP のライブラリとして共通化され、提供されている
• OTP で提供されているライブラリに触れる前に、そのライブラリの背景にある考え方を学ぶ
• Client/Server
• sample/server_client/server_client.erl
• サンプルは簡易的な Key-Value Store
• Client/Server
• sample/server_client/server_client.erl
• get/1 は同期
• self/0, make_ref/1 の使い方に注意
• set/2 は非同期
• Client/Server
• sample/server_client/server_client_pm.erl
• 機能は、server_client.erl と同様
• Parameterized Module にしてある
• Client/Server
• sample/server_client/server_client_pm.erl
• register/2 で名前を付けていないので…
• 多重起動可能
• リモートノードで起動可能
• Client/Server
• sample/server_client/server_client_tx.erl
• 参照と更新を不分割に行える
• プロセスが逐次処理である事と、メッセージとして関数が送れる事を利用
• Finite State Machine
• sample/fsm/finite_state_machine.erl
• 関数 = 状態
• 同じメッセージを受けても状態により処理内容が異なる
• Finite State Machine
• sample/fsm/mutex.erl
• Mutex Semaphore
• Finite State Machine
• sample/fsm/mutex.erl
• ロック中に他のプロセスからロックされるとアンロックされるまで保留される
• プロセスが逐次処理である事と、メッセージを選択受信できる事を利用
• Event Managers and Handlers
• sample/event/*.erl
• イベントが発生した際に、関連する処理を次々に実行
• Event Managers and Handlers
• sample/event/*.erl
• 自作 Behaviour の定義
• 末尾再帰によるステータス維持
• try catch
• Supervisor Tree
• sample/supervisor/my_supervisor.erl
• 作業プロセスの管理
• normal, undef 以外で停止すると再起動
• 一斉停止
本日の流れ
• 準備(心構え)
• Erlang プロセス概要
• プロセスのデザインパターン
• ハンズオン / フリートーク
• やる事や、質問事項が決まっていない方への課題
• server_client_tx.erl をトランザクション対応する
• ヒント:プロセスディクショナリは get/0 で全データを取得できる
• やる事や、質問事項が決まっていない方への課題
• sample/tcp_server を使った echo サーバを実装
• 実装が終わったら tcp_server の仕組みを確認
• 上級者の方は…
• Supervisor で再起動されたプロセスが、再起動前のステータスを引き継ぐ方法を考え、実装
• ヒント: ets を使うと楽
• 上級者の方は…
• gen_tcp や gen_udp を使用するプロセスを
Supervisor の下に配置するコードを記述
• ヒント:処理が中断するプロセスは
gen_server と相性が悪いので proc_lib を利用すると良い